Skip to content

Commit

Permalink
Merge pull request #1703 from kuzudb/rust-api-windows
Browse files Browse the repository at this point in the history
Rust API on Windows
  • Loading branch information
andyfengHKU committed Jun 26, 2023
2 parents ea9c21e + 9d6d9a2 commit ddb531f
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 25 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
make test NUM_THREADS=18
- name: Rust test
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
set OPENSSL_DIR=C:\Program Files\OpenSSL-Win64
make rusttest NUM_THREADS=18
clang-formatting-check:
name: clang-format check
runs-on: kuzu-self-hosted-testing
Expand Down
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,23 @@ arrow:
cmake $(FORCE_COLOR) $(SANITIZER_FLAG) $(GENERATOR) -DCMAKE_BUILD_TYPE=Release .. && \
cmake --build . --config Release -- -j $(NUM_THREADS)

arrow-debug:
ifeq ($(OS),Windows_NT)
$(call mkdirp,external/build) && cd external/build && \
cmake $(FORCE_COLOR) $(SANITIZER_FLAG) $(GENERATOR) -DCMAKE_BUILD_TYPE=Debug .. && \
cmake --build . --config Debug -- -j $(NUM_THREADS)
else
$(call mkdirp,external/build) && cd external/build && \
cmake $(FORCE_COLOR) $(SANITIZER_FLAG) $(GENERATOR) -DCMAKE_BUILD_TYPE=Release .. && \
cmake --build . --config Release -- -j $(NUM_THREADS)
endif

release: arrow
$(call mkdirp,build/release) && cd build/release && \
cmake $(GENERATOR) $(FORCE_COLOR) $(SANITIZER_FLAG) -DCMAKE_BUILD_TYPE=Release ../.. && \
cmake --build . --config Release -- -j $(NUM_THREADS)

debug: arrow
debug: arrow-debug
$(call mkdirp,build/debug) && cd build/debug && \
cmake $(GENERATOR) $(FORCE_COLOR) $(SANITIZER_FLAG) -DCMAKE_BUILD_TYPE=Debug ../.. && \
cmake --build . --config Debug -- -j $(NUM_THREADS)
Expand Down Expand Up @@ -111,6 +122,8 @@ rusttest:
ifeq ($(OS),Windows_NT)
cd $(ROOT_DIR)/tools/rust_api && \
set KUZU_TESTING=1 && \
set CFLAGS=/MDd && \
set CXXFLAGS=/MDd /std:c++20 && \
cargo test -- --test-threads=1
else
cd $(ROOT_DIR)/tools/rust_api && \
Expand Down
2 changes: 2 additions & 0 deletions tools/rust_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ time = "0.3"
[build-dependencies]
cxx-build = "1.0"
num_cpus = "1.0"
[target.'cfg(windows)'.build-dependencies]
which = "4"

[dev-dependencies]
tempdir = "0.3"
Expand Down
113 changes: 94 additions & 19 deletions tools/rust_api/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,44 @@ fn link_mode() -> &'static str {
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
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 = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("kuzu-src");
let target = env::var("PROFILE")?;
let kuzu_root = {
let root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("kuzu-src");
if 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("../..")
}
};
// 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.
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={}", num_cpus::get())])
.args(&[&target, &format!("NUM_THREADS={}", threads)])
.current_dir(&kuzu_root);
let make_status = command.status()?;
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");
Expand Down Expand Up @@ -58,20 +84,67 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

println!("cargo:rustc-link-lib={}=kuzu", link_mode());
if link_mode() == "static" {
println!("cargo:rustc-link-lib=dylib=stdc++");
if cfg!(windows) {
if target == "debug" {
println!("cargo:rustc-link-lib=dylib=msvcrtd");
} else {
println!("cargo:rustc-link-lib=dylib=msvcrt");
}
println!("cargo:rustc-link-lib=dylib=shell32");
println!("cargo:rustc-link-lib=dylib=ole32");
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
}

println!("cargo:rustc-link-lib=static=arrow_bundled_dependencies");
// Dependencies of arrow's bundled dependencies
// arrow's bundled dependencies link against openssl when it's on the system, whether
// requested or not.
// Only seems to be necessary when building tests.
// This will probably not work on windows/macOS
// openssl-sys has better cross-platform logic, but just using that doesn't work.
if env::var("KUZU_TESTING").is_ok() {
println!("cargo:rustc-link-lib=dylib=ssl");
println!("cargo:rustc-link-lib=dylib=crypto");
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()
);
}
println!("cargo:rustc-link-lib=dylib=libssl");
println!("cargo:rustc-link-lib=dylib=libcrypto");
} else {
println!("cargo:rustc-link-lib=dylib=ssl");
println!("cargo:rustc-link-lib=dylib=crypto");
}
}

println!("cargo:rustc-link-lib=static=parquet");
println!("cargo:rustc-link-lib=static=arrow");
if cfg!(windows) {
println!("cargo:rustc-link-lib=static=parquet_static");
println!("cargo:rustc-link-lib=static=arrow_static");
} else {
println!("cargo:rustc-link-lib=static=parquet");
println!("cargo:rustc-link-lib=static=arrow");
}

println!("cargo:rustc-link-lib=static=utf8proc");
println!("cargo:rustc-link-lib=static=antlr4_cypher");
Expand All @@ -83,11 +156,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=include/kuzu_rs.h");
println!("cargo:rerun-if-changed=include/kuzu_rs.cpp");

cxx_build::bridge("src/ffi.rs")
.file("src/kuzu_rs.cpp")
.flag_if_supported("-std=c++20")
.includes(include_paths)
.compile("kuzu_rs");
let mut build = cxx_build::bridge("src/ffi.rs");
build.file("src/kuzu_rs.cpp").includes(include_paths);

Ok(())
if cfg!(windows) {
build.flag("/std:c++20");
} else {
build.flag_if_supported("-std=c++20");
}
build.compile("kuzu_rs");
}
19 changes: 15 additions & 4 deletions tools/rust_api/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,20 @@ mod tests {
let result: Error = Database::new("", 0)
.expect_err("An empty string should not be a valid database path!")
.into();
assert_eq!(
result.to_string(),
"Failed to create directory due to: filesystem error: cannot create directory: No such file or directory []"
);
if cfg!(windows) {
assert_eq!(
result.to_string(),
"Failed to create directory due to: create_directory: The system cannot find the path specified.: \"\""
);
} else if cfg!(linux) {
assert_eq!(
result.to_string(),
"Failed to create directory due to: filesystem error: cannot create directory: No such file or directory []"
);
} else {
assert!(result
.to_string()
.starts_with("Failed to create directory due to"));
}
}
}
7 changes: 6 additions & 1 deletion tools/rust_api/src/query_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,12 @@ mod tests {
CSVOptions::default().delimiter(','),
)?;
let data = std::fs::read_to_string(path.join("output.csv"))?;
assert_eq!(data, "Alice,25\n");
if cfg!(windows) {
// Windows translates the newlines automatically in text mode
assert_eq!(data, "Alice,25\r\n");
} else {
assert_eq!(data, "Alice,25\n");
}
temp_dir.close()?;
Ok(())
}
Expand Down

0 comments on commit ddb531f

Please sign in to comment.