372 lines
13 KiB
Rust
372 lines
13 KiB
Rust
// Copyright 2024 KVCache.AI
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
fn push_existing_dir(search_dirs: &mut Vec<PathBuf>, dir: PathBuf) {
|
|
if dir.is_dir() && !search_dirs.iter().any(|existing| existing == &dir) {
|
|
search_dirs.push(dir);
|
|
}
|
|
}
|
|
|
|
fn push_env_paths(search_dirs: &mut Vec<PathBuf>, name: &str) {
|
|
if let Some(value) = env::var_os(name) {
|
|
for dir in env::split_paths(&value) {
|
|
push_existing_dir(search_dirs, dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn push_cmake_prefix_lib_dirs(search_dirs: &mut Vec<PathBuf>, prefix: PathBuf) {
|
|
push_existing_dir(search_dirs, prefix.join("lib"));
|
|
push_existing_dir(search_dirs, prefix.join("lib64"));
|
|
}
|
|
|
|
fn push_cmake_prefix_paths(search_dirs: &mut Vec<PathBuf>, value: &str) {
|
|
for prefix in value.split(';').filter(|prefix| !prefix.is_empty()) {
|
|
push_cmake_prefix_lib_dirs(search_dirs, PathBuf::from(prefix));
|
|
}
|
|
}
|
|
|
|
fn push_cmake_cache_library_dirs(search_dirs: &mut Vec<PathBuf>, build_dir: &PathBuf) {
|
|
let cache_path = build_dir.join("CMakeCache.txt");
|
|
let Ok(cache) = fs::read_to_string(cache_path) else {
|
|
return;
|
|
};
|
|
|
|
for line in cache.lines() {
|
|
let Some((key, value)) = line.split_once('=') else {
|
|
continue;
|
|
};
|
|
|
|
if key == "CMAKE_PREFIX_PATH:PATH" {
|
|
push_cmake_prefix_paths(search_dirs, value);
|
|
continue;
|
|
}
|
|
|
|
if key.ends_with("_DIR:PATH") {
|
|
let package_dir = PathBuf::from(value);
|
|
if package_dir.parent().and_then(|dir| dir.file_name())
|
|
== Some(std::ffi::OsStr::new("cmake"))
|
|
{
|
|
if let Some(lib_dir) = package_dir.ancestors().nth(2) {
|
|
push_existing_dir(search_dirs, lib_dir.to_path_buf());
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if key.ends_with("_LIBRARY:FILEPATH") || key.ends_with("_LIBRARIES:FILEPATH") {
|
|
let library_path = PathBuf::from(value);
|
|
if let Some(parent) = library_path.parent() {
|
|
push_existing_dir(search_dirs, parent.to_path_buf());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn has_library(search_dirs: &[PathBuf], candidates: &[&str]) -> bool {
|
|
search_dirs.iter().any(|dir| {
|
|
candidates.iter().any(|candidate| {
|
|
["a", "so", "dylib"]
|
|
.into_iter()
|
|
.map(|ext| dir.join(format!("lib{candidate}.{ext}")))
|
|
.any(|path| path.exists())
|
|
})
|
|
})
|
|
}
|
|
|
|
fn emit_link_searches(search_dirs: &[PathBuf]) {
|
|
for dir in search_dirs {
|
|
println!("cargo:rustc-link-search=native={}", dir.display());
|
|
}
|
|
}
|
|
|
|
fn emit_runtime_rpaths(search_dirs: &[PathBuf]) {
|
|
for dir in search_dirs {
|
|
let dir_str = dir.display();
|
|
// Use rustc-link-arg (not -tests) so that the rpath is also applied
|
|
// to the lib-test binary produced by `cargo test` for #[cfg(test)]
|
|
// modules inside src/lib.rs.
|
|
println!("cargo:rustc-link-arg=-Wl,-rpath,{dir_str}");
|
|
}
|
|
}
|
|
|
|
fn compiler_candidates() -> Vec<String> {
|
|
let mut tools = Vec::new();
|
|
|
|
for env_var in ["CC", "CXX"] {
|
|
if let Ok(value) = env::var(env_var) {
|
|
let tool = value.trim();
|
|
if !tool.is_empty() && !tools.iter().any(|existing| existing == tool) {
|
|
tools.push(tool.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
for tool in ["gcc", "cc", "clang", "c++"] {
|
|
if !tools.iter().any(|existing| existing == tool) {
|
|
tools.push(tool.to_string());
|
|
}
|
|
}
|
|
|
|
tools
|
|
}
|
|
|
|
fn compiler_runtime_library(file_name: &str) -> Option<PathBuf> {
|
|
for tool in compiler_candidates() {
|
|
let output = Command::new(&tool)
|
|
.arg(format!("-print-file-name={file_name}"))
|
|
.output()
|
|
.ok()?;
|
|
|
|
if !output.status.success() {
|
|
continue;
|
|
}
|
|
|
|
let path = String::from_utf8(output.stdout).ok()?;
|
|
let path = PathBuf::from(path.trim());
|
|
if path.as_os_str().is_empty() || path == PathBuf::from(file_name) || !path.exists() {
|
|
continue;
|
|
}
|
|
|
|
return Some(path);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn add_compiler_runtime_search_dir(search_dirs: &mut Vec<PathBuf>, file_name: &str) -> bool {
|
|
let Some(path) = compiler_runtime_library(file_name) else {
|
|
return false;
|
|
};
|
|
|
|
if let Some(parent) = path.parent() {
|
|
push_existing_dir(search_dirs, parent.to_path_buf());
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn main() {
|
|
// -----------------------------------------------------------------------
|
|
// Library search path
|
|
//
|
|
// When built via CMake (WITH_STORE_RUST=ON) the CMakeLists.txt injects
|
|
// MOONCAKE_STORE_LIB_DIR pointing at the directory that contains
|
|
// libmooncake_store.a/.so. When cargo is invoked standalone the caller
|
|
// should set the variable manually or rely on the default convention of a
|
|
// sibling `build/` directory produced by a top-level CMake configure.
|
|
// -----------------------------------------------------------------------
|
|
let lib_dir = env::var("MOONCAKE_STORE_LIB_DIR")
|
|
.unwrap_or_else(|_| "../../build/mooncake-store/src".to_string());
|
|
|
|
println!("cargo:rustc-link-search=native={lib_dir}");
|
|
|
|
// mooncake_store depends on libasio.so (shared) built in mooncake-common.
|
|
let lib_path = PathBuf::from(&lib_dir);
|
|
let build_dir = lib_path.ancestors().nth(2).map(PathBuf::from).unwrap_or_else(|| {
|
|
println!("cargo:warning=MOONCAKE_STORE_LIB_DIR='{lib_dir}' does not have enough parent directories; using current directory");
|
|
PathBuf::from(".")
|
|
});
|
|
println!(
|
|
"cargo:rustc-link-search=native={}",
|
|
build_dir.join("mooncake-common").display()
|
|
);
|
|
// mooncake_common static library lives in the src/ subdirectory.
|
|
println!(
|
|
"cargo:rustc-link-search=native={}",
|
|
build_dir.join("mooncake-common/src").display()
|
|
);
|
|
|
|
// transfer_engine is built in a sibling directory.
|
|
println!(
|
|
"cargo:rustc-link-search=native={}",
|
|
build_dir.join("mooncake-transfer-engine/src").display()
|
|
);
|
|
|
|
// common/base library (contains mooncake::Status etc.)
|
|
println!(
|
|
"cargo:rustc-link-search=native={}",
|
|
build_dir.join("mooncake-transfer-engine/src/common/base").display()
|
|
);
|
|
|
|
// CUDA runtime libraries (needed by transfer_engine RDMA transport).
|
|
let cuda_home = env::var("CUDA_HOME")
|
|
.or_else(|_| env::var("CUDA_PATH"))
|
|
.unwrap_or_else(|_| "/usr/local/cuda".to_string());
|
|
println!("cargo:rustc-link-search=native={}/targets/x86_64-linux/lib", cuda_home);
|
|
|
|
// cachelib_memory_allocator is a static library built alongside mooncake_store.
|
|
println!(
|
|
"cargo:rustc-link-search=native={}",
|
|
build_dir.join("mooncake-store/src/cachelib_memory_allocator").display()
|
|
);
|
|
|
|
println!("cargo:rustc-link-lib=mooncake_store");
|
|
|
|
// Dependencies of mooncake_store that must be satisfied at link time.
|
|
// The list mirrors what mooncake-store/src/CMakeLists.txt links against.
|
|
println!("cargo:rustc-link-lib=transfer_engine");
|
|
println!("cargo:rustc-link-lib=mooncake_common"); // Environ::Get() and other common utilities
|
|
println!("cargo:rustc-link-lib=base"); // mooncake::Status etc.
|
|
println!("cargo:rustc-link-lib=asio"); // shared library built by mooncake-common
|
|
println!("cargo:rustc-link-lib=jsoncpp"); // transfer_engine dependency
|
|
println!("cargo:rustc-link-lib=cachelib_memory_allocator"); // static
|
|
println!("cargo:rustc-link-lib=stdc++");
|
|
println!("cargo:rustc-link-lib=glog");
|
|
println!("cargo:rustc-link-lib=gflags");
|
|
println!("cargo:rustc-link-lib=numa"); // NUMA binding
|
|
println!("cargo:rustc-link-lib=curl"); // HTTP metadata plugin
|
|
println!("cargo:rustc-link-lib=ibverbs"); // RDMA transport
|
|
println!("cargo:rustc-link-lib=pthread");
|
|
println!("cargo:rustc-link-lib=xxhash");
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Header path for bindgen
|
|
// -----------------------------------------------------------------------
|
|
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing CARGO_MANIFEST_DIR"));
|
|
let mut search_dirs = Vec::new();
|
|
|
|
let explicit_lib_dir = env::var("MOONCAKE_STORE_LIB_DIR").ok().map(PathBuf::from);
|
|
if let Some(dir) = explicit_lib_dir.clone() {
|
|
push_existing_dir(&mut search_dirs, dir);
|
|
}
|
|
|
|
if let Ok(build_dir) = env::var("MOONCAKE_BUILD_DIR") {
|
|
let build_dir = PathBuf::from(build_dir);
|
|
push_cmake_cache_library_dirs(&mut search_dirs, &build_dir);
|
|
for dir in [
|
|
build_dir.join("mooncake-store/src"),
|
|
build_dir.join("mooncake-store/src/cachelib_memory_allocator"),
|
|
build_dir.join("mooncake-transfer-engine/src"),
|
|
build_dir.join("mooncake-transfer-engine/src/common/base"),
|
|
build_dir.join("mooncake-asio"),
|
|
build_dir.join("mooncake-common/etcd"),
|
|
] {
|
|
push_existing_dir(&mut search_dirs, dir);
|
|
}
|
|
}
|
|
|
|
let default_build_dir = manifest_dir.join("../../build");
|
|
push_cmake_cache_library_dirs(&mut search_dirs, &default_build_dir);
|
|
for dir in [
|
|
default_build_dir.join("mooncake-store/src"),
|
|
default_build_dir.join("mooncake-store/src/cachelib_memory_allocator"),
|
|
default_build_dir.join("mooncake-transfer-engine/src"),
|
|
default_build_dir.join("mooncake-transfer-engine/src/common/base"),
|
|
default_build_dir.join("mooncake-asio"),
|
|
default_build_dir.join("mooncake-common"),
|
|
default_build_dir.join("mooncake-common/etcd"),
|
|
PathBuf::from("/usr/local/lib"),
|
|
PathBuf::from("/usr/lib/x86_64-linux-gnu"),
|
|
PathBuf::from("/lib/x86_64-linux-gnu"),
|
|
] {
|
|
push_existing_dir(&mut search_dirs, dir);
|
|
}
|
|
|
|
if let Ok(cmake_prefix_path) = env::var("CMAKE_PREFIX_PATH") {
|
|
push_cmake_prefix_paths(&mut search_dirs, &cmake_prefix_path);
|
|
}
|
|
|
|
push_env_paths(&mut search_dirs, "LD_LIBRARY_PATH");
|
|
push_env_paths(&mut search_dirs, "LIBRARY_PATH");
|
|
|
|
let asan_runtime_so = compiler_runtime_library("libasan.so");
|
|
if let Some(path) = asan_runtime_so.as_ref() {
|
|
if let Some(parent) = path.parent() {
|
|
push_existing_dir(&mut search_dirs, parent.to_path_buf());
|
|
}
|
|
}
|
|
let has_asan_runtime = asan_runtime_so.is_some()
|
|
|| add_compiler_runtime_search_dir(&mut search_dirs, "libasan.a");
|
|
let has_gcov_runtime = add_compiler_runtime_search_dir(&mut search_dirs, "libgcov.a")
|
|
|| add_compiler_runtime_search_dir(&mut search_dirs, "libgcov.so");
|
|
|
|
emit_link_searches(&search_dirs);
|
|
emit_runtime_rpaths(&search_dirs);
|
|
|
|
if has_asan_runtime || has_library(&search_dirs, &["asan"]) {
|
|
println!("cargo:rustc-link-lib=asan");
|
|
}
|
|
|
|
for library in [
|
|
"mooncake_store",
|
|
"cachelib_memory_allocator",
|
|
"transfer_engine",
|
|
"base",
|
|
"asio",
|
|
"stdc++",
|
|
"glog",
|
|
"gflags",
|
|
"pthread",
|
|
"xxhash",
|
|
"numa",
|
|
"ibverbs",
|
|
"jsoncpp",
|
|
"zstd",
|
|
"m",
|
|
] {
|
|
println!("cargo:rustc-link-lib={library}");
|
|
}
|
|
|
|
for (link_name, candidates) in [
|
|
("etcd_wrapper", &["etcd_wrapper"] as &[&str]),
|
|
("hiredis", &["hiredis"]),
|
|
("curl", &["curl"]),
|
|
("cuda", &["cuda"]),
|
|
("cudart", &["cudart"]),
|
|
("mlx5", &["mlx5"]), // IBGDA device transport (mlx5 DevX) pulled into transfer_engine, CUDA-only
|
|
("uring", &["uring"]),
|
|
] {
|
|
if has_library(&search_dirs, candidates) {
|
|
println!("cargo:rustc-link-lib={link_name}");
|
|
}
|
|
}
|
|
|
|
if has_gcov_runtime || has_library(&search_dirs, &["gcov"]) {
|
|
println!("cargo:rustc-link-lib=gcov");
|
|
}
|
|
|
|
let include_dir = env::var("MOONCAKE_STORE_INCLUDE_DIR")
|
|
.unwrap_or_else(|_| "../include".to_string());
|
|
|
|
let header = format!("{include_dir}/store_c.h");
|
|
|
|
println!("cargo:rerun-if-changed={header}");
|
|
println!("cargo:rerun-if-env-changed=MOONCAKE_BUILD_DIR");
|
|
println!("cargo:rerun-if-env-changed=MOONCAKE_STORE_LIB_DIR");
|
|
println!("cargo:rerun-if-env-changed=MOONCAKE_STORE_INCLUDE_DIR");
|
|
println!("cargo:rerun-if-env-changed=LD_LIBRARY_PATH");
|
|
println!("cargo:rerun-if-env-changed=LIBRARY_PATH");
|
|
println!("cargo:rerun-if-env-changed=CC");
|
|
println!("cargo:rerun-if-env-changed=CXX");
|
|
|
|
let bindings = bindgen::Builder::default()
|
|
.header(&header)
|
|
.allowlist_function("mooncake_store_.*")
|
|
.allowlist_type("mooncake_.*")
|
|
.generate()
|
|
.expect("Unable to generate Mooncake Store bindings");
|
|
|
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
bindings
|
|
.write_to_file(out_path.join("bindings.rs"))
|
|
.expect("Couldn't write Mooncake Store bindings");
|
|
}
|