Skip to content

Commit 30469d5

Browse files
committed
auto download libchdb
1 parent 0a6d279 commit 30469d5

File tree

3 files changed

+135
-28
lines changed

3 files changed

+135
-28
lines changed

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22
name = "chdb-rust"
33
version = "1.0.0"
44
edition = "2021"
5+
authors = ["Auxten"]
6+
description = "chDB FFI bindings for Rust(Experimental)"
7+
homepage = "https://github.com/chdb-io/chdb-rust"
8+
repository = "https://github.com/chdb-io/chdb-rust"
9+
license = "Apache-2.0"
10+
readme = "README.md"
511
keywords = ["clickhouse", "chdb", "database", "embedded", "analytics"]
612

713
[dependencies]
814
thiserror = "1"
915

1016
[build-dependencies]
1117
bindgen = "0.70.1"
18+
reqwest = { version = "0.11", features = ["blocking"] }
19+
flate2 = "1.0"
20+
tar = "0.4"
1221

1322
[dev-dependencies]
1423
tempdir = "0.3.7"

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,28 @@ Experimental [chDB](https://github.com/chdb-io/chdb) FFI bindings for Rust
99
## Status
1010

1111
- Experimental, unstable, subject to changes
12-
- Requires [`libchdb`](https://github.com/chdb-io/chdb) on the system. You can install the compatible version from
13-
`install_libchdb.sh`
12+
- Automatically downloads and manages [`libchdb`](https://github.com/chdb-io/chdb) dependencies during build
1413

1514
## Usage
1615

17-
### Install libchdb
16+
The library automatically downloads the required `libchdb` binary during the build process.
1817

19-
You can install it system-wide
18+
### Supported platforms:
19+
- Linux x86_64
20+
- Linux aarch64
21+
- macOS x86_64
22+
- macOS arm64 (Apple Silicon)
2023

24+
### Manual Installation (Optional)
25+
26+
If you prefer to install `libchdb` manually, you can:
27+
28+
Install it system-wide:
2129
```bash
2230
./update_libchdb.sh --global
2331
```
2432

25-
or use it in a local directory
26-
33+
Or use it in a local directory:
2734
```bash
2835
./update_libchdb.sh --local
2936
```

build.rs

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,124 @@
1-
use std::path::PathBuf;
1+
use std::env;
2+
use std::fs;
3+
use std::path::{Path, PathBuf};
4+
use std::process::Command;
25

36
fn main() {
4-
// Tell cargo to look for shared libraries in the specified directory
5-
println!("cargo:rustc-link-search=./");
7+
let out_dir = env::var("OUT_DIR").unwrap();
8+
let out_path = PathBuf::from(&out_dir);
9+
let libchdb_info = find_libchdb_or_download(&out_path);
10+
match libchdb_info {
11+
Ok((lib_dir, header_path)) => {
12+
setup_link_paths(&lib_dir);
13+
generate_bindings(&header_path, &out_path);
14+
}
15+
Err(e) => {
16+
eprintln!("Failed to find or download libchdb: {}", e);
17+
println!("cargo:warning=Failed to find libchdb. Please install manually using './update_libchdb.sh --local' or '--global'");
18+
std::process::exit(1);
19+
}
20+
}
21+
}
622

7-
// Tell cargo to tell rustc to link the system chdb library.
8-
println!("cargo:rustc-link-lib=chdb");
23+
fn find_libchdb_or_download(out_dir: &Path) -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>> {
24+
if let Some((lib_dir, header_path)) = find_existing_libchdb() {
25+
return Ok((lib_dir, header_path));
26+
}
27+
28+
println!("cargo:warning=libchdb not found locally, attempting to download...");
29+
download_libchdb_to_out_dir(out_dir)?;
30+
let lib_dir = out_dir.to_path_buf();
31+
let header_path = out_dir.join("chdb.h");
32+
33+
if !header_path.exists() {
34+
return Err("Header file not found after download".into());
35+
}
36+
37+
Ok((lib_dir, header_path))
38+
}
939

10-
// Tell cargo to invalidate the built crate whenever the wrapper changes.
11-
println!("cargo:rerun-if-changed=chdb.h");
40+
fn find_existing_libchdb() -> Option<(PathBuf, PathBuf)> {
41+
if Path::new("./libchdb.so").exists() && Path::new("./chdb.h").exists() {
42+
return Some((PathBuf::from("."), PathBuf::from("./chdb.h")));
43+
}
44+
45+
// Check system installation
46+
let system_lib_path = Path::new("/usr/local/lib");
47+
let system_header_path = Path::new("/usr/local/include/chdb.h");
48+
49+
if system_header_path.exists() {
50+
if system_lib_path.join("libchdb.so").exists() ||
51+
system_lib_path.join("libchdb.dylib").exists() {
52+
return Some((system_lib_path.to_path_buf(), system_header_path.to_path_buf()));
53+
}
54+
}
55+
56+
None
57+
}
58+
59+
fn download_libchdb_to_out_dir(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
60+
let platform = get_platform_string()?;
61+
let version = "v2.1.1";
62+
let url = format!(
63+
"https://github.com/chdb-io/chdb/releases/download/{}/{}",
64+
version, platform
65+
);
66+
println!("cargo:warning=Downloading libchdb from: {}", url);
67+
let response = reqwest::blocking::get(&url)?;
68+
let content = response.bytes()?;
69+
let temp_archive = out_dir.join("libchdb.tar.gz");
70+
fs::write(&temp_archive, content)?;
71+
let file = fs::File::open(&temp_archive)?;
72+
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(file));
73+
archive.unpack(out_dir)?;
74+
fs::remove_file(&temp_archive)?;
75+
if cfg!(unix) {
76+
let lib_path = out_dir.join("libchdb.so");
77+
if lib_path.exists() {
78+
let _ = Command::new("chmod")
79+
.args(&["+x", lib_path.to_str().unwrap()])
80+
.output();
81+
}
82+
}
83+
println!("cargo:warning=libchdb downloaded successfully to OUT_DIR");
84+
Ok(())
85+
}
1286

13-
// The bindgen::Builder is the main entry point
14-
// to bindgen, and lets you build up options for
15-
// the resulting bindings.
87+
fn get_platform_string() -> Result<String, &'static str> {
88+
let os = env::consts::OS;
89+
let arch = env::consts::ARCH;
90+
match (os, arch) {
91+
("linux", "x86_64") => Ok("linux-x86_64-libchdb.tar.gz".to_string()),
92+
("linux", "aarch64") => Ok("linux-aarch64-libchdb.tar.gz".to_string()),
93+
("macos", "x86_64") => Ok("macos-x86_64-libchdb.tar.gz".to_string()),
94+
("macos", "aarch64") => Ok("macos-arm64-libchdb.tar.gz".to_string()),
95+
_ => Err("Unsupported platform"),
96+
}
97+
}
98+
99+
fn setup_link_paths(lib_dir: &Path) {
100+
println!("cargo:rustc-link-search={}", lib_dir.display());
101+
println!("cargo:rustc-link-search=./");
102+
println!("cargo:rustc-link-search=/usr/local/lib");
103+
println!("cargo:rustc-link-lib=chdb");
104+
println!("cargo:rerun-if-changed=wrapper.h");
105+
println!("cargo:rerun-if-changed=build.rs");
106+
}
107+
108+
fn generate_bindings(header_path: &Path, out_dir: &Path) {
109+
let wrapper_content = format!("#include \"{}\"", header_path.display());
110+
let temp_wrapper = out_dir.join("temp_wrapper.h");
111+
fs::write(&temp_wrapper, wrapper_content).expect("Failed to write temp wrapper");
16112
let bindings = bindgen::Builder::default()
17-
// The input header we would like to generate
18-
// bindings for.
19-
.header("wrapper.h")
20-
// Tell cargo to invalidate the built crate whenever any of the
21-
// included header files changed.
113+
.header(temp_wrapper.to_str().unwrap())
22114
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
23-
// Finish the builder and generate the bindings.
24115
.generate()
25-
// Unwrap the Result and panic on failure.
26116
.expect("Unable to generate bindings");
27-
28-
// Write the bindings to the $OUT_DIR/bindings.rs file.
29-
let out_path = PathBuf::from("./src/");
117+
let src_path = PathBuf::from("./src/");
118+
bindings
119+
.write_to_file(src_path.join("bindings.rs"))
120+
.expect("Couldn't write bindings to src!");
30121
bindings
31-
.write_to_file(out_path.join("bindings.rs"))
32-
.expect("Couldn't write bindings!");
122+
.write_to_file(out_dir.join("bindings.rs"))
123+
.expect("Couldn't write bindings to OUT_DIR!");
33124
}

0 commit comments

Comments
 (0)