Skip to content

Commit

Permalink
Non-bundled rust build
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminwinger committed Jul 3, 2023
1 parent 472fab9 commit 5a21bec
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 101 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ ifeq ($(OS),Windows_NT)
cargo test -- --test-threads=1
else
cd $(ROOT_DIR)/tools/rust_api && \
KUZU_TESTING=1 cargo test -- --test-threads=1
CARGO_BUILD_JOBS=$(NUM_THREADS) KUZU_TESTING=1 cargo test -- --test-threads=1
endif

clean-python-api:
Expand Down
28 changes: 28 additions & 0 deletions examples/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/rust_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ time = "0.3"
[build-dependencies]
cxx-build = "1.0"
num_cpus = "1.0"
cmake = "0.1"
[target.'cfg(windows)'.build-dependencies]
which = "4"

Expand Down
232 changes: 133 additions & 99 deletions tools/rust_api/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::env;
use std::path::Path;
use std::path::{Path, PathBuf};

fn link_mode() -> &'static str {
if env::var("KUZU_SHARED").is_ok() {
Expand All @@ -9,79 +9,43 @@ fn link_mode() -> &'static str {
}
}

fn main() {
// There is a kuzu-src symlink pointing to the root of the repo since Cargo
// only looks at the files within the rust project when packaging crates.
// Using a symlink the library can both be built in-source and from a crate.
let kuzu_root = {
let root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("kuzu-src");
if root.is_dir() {
root
fn find_openssl_windows() {
// Find openssl library relative to the path of the openssl executable
// Or fall back to OPENSSL_DIR
#[cfg(windows)]
{
let openssl_dir = if let Ok(mut path) = which::which("openssl") {
path.pop();
path.pop();
path
} else if let Ok(path) = env::var("OPENSSL_CONF") {
Path::new(&path)
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
} else if let Ok(path) = env::var("OPENSSL_DIR") {
Path::new(&path).to_path_buf()
} else {
// If the path is not directory, this is probably an in-source build on windows where the
// symlink is unreadable.
Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../..")
}
};
// Windows fails to link on windows unless CFLAGS and CXXFLAGS are overridden
// If they are, assume the user knows what they are doing. Otherwise just link against the
// release version of kuzu even in debug mode.
panic!(
"OPENSSL_DIR must be set if the openssl library cannot be found \
using the path of the openssl executable"
)
};
println!(
"cargo:rustc-link-search=native={}/lib",
openssl_dir.display()
);
}
}

fn link_libraries() {
let target = if cfg!(windows) && std::env::var("CXXFLAGS").is_err() {
"release".to_string()
} else {
env::var("PROFILE").unwrap()
};
let kuzu_cmake_root = kuzu_root.join(format!("build/{target}"));
let mut command = std::process::Command::new("make");
let threads = env::var("NUM_THREADS")
.map(|x| x.parse::<usize>())
.unwrap_or_else(|_| Ok(num_cpus::get()))
.unwrap();
command
.args(&[&target, &format!("NUM_THREADS={}", threads)])
.current_dir(&kuzu_root);
let make_status = command.status().unwrap_or_else(|_| {
panic!(
"Running make {} on {}/Makefile failed!",
&target,
&kuzu_root.display()
)
});
assert!(make_status.success());

let kuzu_lib_path = kuzu_cmake_root.join("src");

println!("cargo:rustc-link-search=native={}", kuzu_lib_path.display());

let include_paths = vec![
Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("include"),
kuzu_root.join("src/include"),
kuzu_root.join("third_party/nlohmann_json"),
kuzu_root.join("third_party/spdlog"),
];
for dir in ["utf8proc", "antlr4_cypher", "antlr4_runtime", "re2"] {
let lib_path = kuzu_cmake_root
.join(format!("third_party/{dir}"))
.canonicalize()
.unwrap_or_else(|_| {
panic!(
"Could not find {}/third_party/{dir}",
kuzu_cmake_root.display()
)
});
println!("cargo:rustc-link-search=native={}", lib_path.display());
}

let arrow_install = kuzu_root.join("external/build/arrow/install");
println!(
"cargo:rustc-link-search=native={}",
arrow_install.join("lib").display()
);
println!(
"cargo:rustc-link-search=native={}",
arrow_install.join("lib64").display()
);

println!("cargo:rustc-link-lib={}=kuzu", link_mode());
if link_mode() == "static" {
if cfg!(windows) {
Expand All @@ -102,34 +66,7 @@ fn main() {
// Only seems to be necessary when building tests.
if env::var("KUZU_TESTING").is_ok() {
if cfg!(windows) {
// Find openssl library relative to the path of the openssl executable
// Or fall back to OPENSSL_DIR
#[cfg(windows)]
{
let openssl_dir = if let Ok(mut path) = which::which("openssl") {
path.pop();
path.pop();
path
} else if let Ok(path) = env::var("OPENSSL_CONF") {
Path::new(&path)
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
} else if let Ok(path) = env::var("OPENSSL_DIR") {
Path::new(&path).to_path_buf()
} else {
panic!(
"OPENSSL_DIR must be set if the openssl library cannot be found \
using the path of the openssl executable"
)
};
println!(
"cargo:rustc-link-search=native={}/lib",
openssl_dir.display()
);
}
find_openssl_windows();
println!("cargo:rustc-link-lib=dylib=libssl");
println!("cargo:rustc-link-lib=dylib=libcrypto");
} else {
Expand All @@ -151,14 +88,111 @@ fn main() {
println!("cargo:rustc-link-lib=static=antlr4_runtime");
println!("cargo:rustc-link-lib=static=re2");
}
}

fn build_bundled_cmake() -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
if let Ok(jobs) = std::env::var("NUM_THREADS") {
std::env::set_var("NUM_JOBS", jobs);
}
let kuzu_root = {
let root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("kuzu-src");
if root.is_symlink() || root.is_dir() {
root
} else {
// If the path is not directory, this is probably an in-source build on windows where the
// symlink is unreadable.
Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../..")
}
};
let arrow_build_dir = cmake::Config::new(kuzu_root.join("external"))
.no_build_target(true)
// Needs separate out directory so they don't clobber each other
.out_dir(Path::new(&env::var("OUT_DIR").unwrap()).join("build-arrow"))
.build();

let arrow_install = arrow_build_dir.join("build/arrow/install");
println!(
"cargo:rustc-link-search=native={}",
arrow_install.join("lib").display()
);
println!(
"cargo:rustc-link-search=native={}",
arrow_install.join("lib64").display()
);

let build_dir = cmake::Config::new(&kuzu_root)
.no_build_target(true)
.define("BUILD_SHELL", "OFF")
.define("BUILD_PYTHON_API", "OFF")
.define("ARROW_INSTALL", &arrow_install)
.build();

let kuzu_lib_path = build_dir.join("build").join("src");
println!(
"cargo:warning=Kuzu build directory: {}",
build_dir.display()
);
println!(
"cargo:warning=Kuzu library path directory: {}",
kuzu_lib_path.display()
);

println!("cargo:rustc-link-search=native={}", kuzu_lib_path.display());

for dir in ["utf8proc", "antlr4_cypher", "antlr4_runtime", "re2"] {
let lib_path = build_dir
.join("build")
.join("third_party")
.join(dir)
.canonicalize()
.unwrap_or_else(|_| {
panic!(
"Could not find {}/build/third_party/{}",
build_dir.display(),
dir
)
});
println!("cargo:rustc-link-search=native={}", lib_path.display());
}

Ok(vec![
kuzu_root.join("src/include"),
kuzu_root.join("third_party/nlohmann_json"),
kuzu_root.join("third_party/spdlog"),
arrow_install.join("include"),
])
}

fn main() {
let mut build = cxx_build::bridge("src/ffi.rs");
build.file("src/kuzu_rs.cpp");

let mut include_paths =
vec![Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("include")];

if let (Ok(kuzu_lib_dir), Ok(kuzu_include)) =
(env::var("KUZU_LIBRARY_DIR"), env::var("KUZU_INCLUDE_DIR"))
{
println!("cargo:rustc-link-search=native={}", kuzu_lib_dir);
if cfg!(windows) && link_mode() == "dylib" {
println!("cargo:rustc-link-lib=dylib=kuzu_shared");
} else {
println!("cargo:rustc-link-lib={}=kuzu", link_mode());
}
include_paths.push(Path::new(&kuzu_include).to_path_buf());
} else {
include_paths.extend(build_bundled_cmake().expect("Bundled build failed!"));
build.define("KUZU_BUNDLED", None);
}
build.includes(include_paths);

link_libraries();

println!("cargo:rerun-if-env-changed=KUZU_SHARED");

println!("cargo:rerun-if-changed=include/kuzu_rs.h");
println!("cargo:rerun-if-changed=include/kuzu_rs.cpp");

let mut build = cxx_build::bridge("src/ffi.rs");
build.file("src/kuzu_rs.cpp").includes(include_paths);

if cfg!(windows) {
build.flag("/std:c++20");
} else {
Expand Down
6 changes: 5 additions & 1 deletion tools/rust_api/include/kuzu_rs.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#pragma once
#include <memory>

#include "main/kuzu.h"
#include "rust/cxx.h"
#ifdef KUZU_BUNDLED
#include "main/kuzu.h"
#else
#include <kuzu.hpp>
#endif

namespace kuzu_rs {

Expand Down
11 changes: 11 additions & 0 deletions tools/rust_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@
//! Generally, use of of this API is safe, however creating multiple databases in the same
//! scope is not safe.
//! If you need to access multiple databases you will need to do so in separate processes.
//!
//! ## Building
//!
//! By default, the kuzu C++ library will be compiled from source and statically linked.
//!
//! If you want to instead link against a pre-built version of the library, the following environment
//! variables can be used to configure the build process:
//!
//! - `KUZU_SHARED`: If set, link dynamically instead of statically
//! - `KUZU_INCLUDE_DIR`: Directory of kuzu's headers
//! - `KUZU_LIBRARY_DIR`: Directory containing kuzu's pre-built libraries.

mod connection;
mod database;
Expand Down

0 comments on commit 5a21bec

Please sign in to comment.