Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
[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]
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"
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
135 changes: 113 additions & 22 deletions build.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<String, &'static str> {
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!");
}