diff --git a/Cargo.toml b/Cargo.toml index 5ea76bd..996ad2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "chdb-rust" -version = "1.0.0" +version = "1.1.0" edition = "2021" +authors = ["Auxten"] +description = "chDB FFI bindings for Rust(Experimental)" +homepage = "https://github.com/chdb-io/chdb-rust" +repository = "https://github.com/chdb-io/chdb-rust" +license = "Apache-2.0" +readme = "README.md" keywords = ["clickhouse", "chdb", "database", "embedded", "analytics"] [dependencies] @@ -9,6 +15,9 @@ thiserror = "1" [build-dependencies] bindgen = "0.70.1" +reqwest = { version = "0.11", features = ["blocking"] } +flate2 = "1.0" +tar = "0.4" [dev-dependencies] tempdir = "0.3.7" \ No newline at end of file diff --git a/README.md b/README.md index a8b9ab5..01d1f21 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,28 @@ Experimental [chDB](https://github.com/chdb-io/chdb) FFI bindings for Rust ## Status - Experimental, unstable, subject to changes -- Requires [`libchdb`](https://github.com/chdb-io/chdb) on the system. You can install the compatible version from - `install_libchdb.sh` +- Automatically downloads and manages [`libchdb`](https://github.com/chdb-io/chdb) dependencies during build ## Usage -### Install libchdb +The library automatically downloads the required `libchdb` binary during the build process. -You can install it system-wide +### Supported platforms: +- Linux x86_64 +- Linux aarch64 +- macOS x86_64 +- macOS arm64 (Apple Silicon) +### Manual Installation (Optional) + +If you prefer to install `libchdb` manually, you can: + +Install it system-wide: ```bash ./update_libchdb.sh --global ``` -or use it in a local directory - +Or use it in a local directory: ```bash ./update_libchdb.sh --local ``` diff --git a/build.rs b/build.rs index 3e748b4..82c8f78 100644 --- a/build.rs +++ b/build.rs @@ -1,33 +1,124 @@ -use std::path::PathBuf; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; fn main() { - // Tell cargo to look for shared libraries in the specified directory - println!("cargo:rustc-link-search=./"); + let out_dir = env::var("OUT_DIR").unwrap(); + let out_path = PathBuf::from(&out_dir); + let libchdb_info = find_libchdb_or_download(&out_path); + match libchdb_info { + Ok((lib_dir, header_path)) => { + setup_link_paths(&lib_dir); + generate_bindings(&header_path, &out_path); + } + Err(e) => { + eprintln!("Failed to find or download libchdb: {}", e); + println!("cargo:warning=Failed to find libchdb. Please install manually using './update_libchdb.sh --local' or '--global'"); + std::process::exit(1); + } + } +} - // Tell cargo to tell rustc to link the system chdb library. - println!("cargo:rustc-link-lib=chdb"); +fn find_libchdb_or_download(out_dir: &Path) -> Result<(PathBuf, PathBuf), Box> { + if let Some((lib_dir, header_path)) = find_existing_libchdb() { + return Ok((lib_dir, header_path)); + } + + println!("cargo:warning=libchdb not found locally, attempting to download..."); + download_libchdb_to_out_dir(out_dir)?; + let lib_dir = out_dir.to_path_buf(); + let header_path = out_dir.join("chdb.h"); + + if !header_path.exists() { + return Err("Header file not found after download".into()); + } + + Ok((lib_dir, header_path)) +} - // Tell cargo to invalidate the built crate whenever the wrapper changes. - println!("cargo:rerun-if-changed=chdb.h"); +fn find_existing_libchdb() -> Option<(PathBuf, PathBuf)> { + if Path::new("./libchdb.so").exists() && Path::new("./chdb.h").exists() { + return Some((PathBuf::from("."), PathBuf::from("./chdb.h"))); + } + + // Check system installation + let system_lib_path = Path::new("/usr/local/lib"); + let system_header_path = Path::new("/usr/local/include/chdb.h"); + + if system_header_path.exists() { + if system_lib_path.join("libchdb.so").exists() || + system_lib_path.join("libchdb.dylib").exists() { + return Some((system_lib_path.to_path_buf(), system_header_path.to_path_buf())); + } + } + + None +} + +fn download_libchdb_to_out_dir(out_dir: &Path) -> Result<(), Box> { + let platform = get_platform_string()?; + let version = "v3.6.0"; + let url = format!( + "https://github.com/chdb-io/chdb/releases/download/{}/{}", + version, platform + ); + println!("cargo:warning=Downloading libchdb from: {}", url); + let response = reqwest::blocking::get(&url)?; + let content = response.bytes()?; + let temp_archive = out_dir.join("libchdb.tar.gz"); + fs::write(&temp_archive, content)?; + let file = fs::File::open(&temp_archive)?; + let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(file)); + archive.unpack(out_dir)?; + fs::remove_file(&temp_archive)?; + if cfg!(unix) { + let lib_path = out_dir.join("libchdb.so"); + if lib_path.exists() { + let _ = Command::new("chmod") + .args(&["+x", lib_path.to_str().unwrap()]) + .output(); + } + } + println!("cargo:warning=libchdb downloaded successfully to OUT_DIR"); + Ok(()) +} - // The bindgen::Builder is the main entry point - // to bindgen, and lets you build up options for - // the resulting bindings. +fn get_platform_string() -> Result { + let os = env::consts::OS; + let arch = env::consts::ARCH; + match (os, arch) { + ("linux", "x86_64") => Ok("linux-x86_64-libchdb.tar.gz".to_string()), + ("linux", "aarch64") => Ok("linux-aarch64-libchdb.tar.gz".to_string()), + ("macos", "x86_64") => Ok("macos-x86_64-libchdb.tar.gz".to_string()), + ("macos", "aarch64") => Ok("macos-arm64-libchdb.tar.gz".to_string()), + _ => Err("Unsupported platform"), + } +} + +fn setup_link_paths(lib_dir: &Path) { + println!("cargo:rustc-link-search={}", lib_dir.display()); + println!("cargo:rustc-link-search=./"); + println!("cargo:rustc-link-search=/usr/local/lib"); + println!("cargo:rustc-link-lib=chdb"); + println!("cargo:rerun-if-changed=wrapper.h"); + println!("cargo:rerun-if-changed=build.rs"); +} + +fn generate_bindings(header_path: &Path, out_dir: &Path) { + let wrapper_content = format!("#include \"{}\"", header_path.display()); + let temp_wrapper = out_dir.join("temp_wrapper.h"); + fs::write(&temp_wrapper, wrapper_content).expect("Failed to write temp wrapper"); let bindings = bindgen::Builder::default() - // The input header we would like to generate - // bindings for. - .header("wrapper.h") - // Tell cargo to invalidate the built crate whenever any of the - // included header files changed. + .header(temp_wrapper.to_str().unwrap()) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - // Finish the builder and generate the bindings. .generate() - // Unwrap the Result and panic on failure. .expect("Unable to generate bindings"); - - // Write the bindings to the $OUT_DIR/bindings.rs file. - let out_path = PathBuf::from("./src/"); + let src_path = PathBuf::from("./src/"); + bindings + .write_to_file(src_path.join("bindings.rs")) + .expect("Couldn't write bindings to src!"); bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("Couldn't write bindings!"); + .write_to_file(out_dir.join("bindings.rs")) + .expect("Couldn't write bindings to OUT_DIR!"); }