This is an automated email from the ASF dual-hosted git repository. silver pushed a commit to branch cpp-binding in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
commit e0a97b8c172517ec2f600d50aeb5be28eb4e18de Author: silver-ymz <[email protected]> AuthorDate: Wed Aug 30 23:18:50 2023 +0800 feat(bindings/cpp): init cpp binding Signed-off-by: silver-ymz <[email protected]> --- Cargo.lock | 78 +++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + bindings/cpp/.gitignore | 3 ++ bindings/cpp/CMakeLists.txt | 77 ++++++++++++++++++++++++++++++++++ Cargo.toml => bindings/cpp/Cargo.toml | 52 ++++++++--------------- bindings/cpp/README.md | 36 ++++++++++++++++ bindings/cpp/build.rs | 22 ++++++++++ bindings/cpp/include/opendal.hpp | 54 ++++++++++++++++++++++++ bindings/cpp/src/lib.rs | 60 +++++++++++++++++++++++++++ bindings/cpp/src/opendal.cpp | 50 ++++++++++++++++++++++ bindings/cpp/tests/basic_test.cpp | 54 ++++++++++++++++++++++++ 11 files changed, 452 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea91cdd01..eb4beb544 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -962,6 +962,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -1223,6 +1233,50 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "cxx" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe98ba1789d56fb3db3bee5e032774d4f421b685de7ba703643584ba24effbe" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ce20f6b8433da4841b1dadfb9468709868022d829d5ca1f2ffbda928455ea3" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.23", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20888d9e1d2298e2ff473cee30efe7d5036e437857ab68bbfea84c74dba91da2" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa16a70dd58129e4dfffdff535fb1bce66673f7bbeec4a5a1765a504e1ccd84" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.23", +] + [[package]] name = "darling" version = "0.14.4" @@ -2625,6 +2679,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3508,6 +3571,15 @@ dependencies = [ "opendal", ] +[[package]] +name = "opendal-cpp" +version = "0.1.0" +dependencies = [ + "cxx", + "cxx-build", + "opendal", +] + [[package]] name = "opendal-dotnet" version = "0.1.0" @@ -5150,6 +5222,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "sct" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 1b891db45..5bc5b746e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "bindings/dotnet", "bindings/ocaml", "bindings/php", + "bindings/cpp", "bin/oli", "bin/oay", diff --git a/bindings/cpp/.gitignore b/bindings/cpp/.gitignore new file mode 100644 index 000000000..c1d315b0e --- /dev/null +++ b/bindings/cpp/.gitignore @@ -0,0 +1,3 @@ +compile_commands.json +.cache +build \ No newline at end of file diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt new file mode 100644 index 000000000..b8b5abc1d --- /dev/null +++ b/bindings/cpp/CMakeLists.txt @@ -0,0 +1,77 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +cmake_minimum_required(VERSION 3.20) +project(opendal-cpp CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CARGO_MANIFEST ${CMAKE_SOURCE_DIR}/Cargo.toml) +set(CARGO_TARGET_DIR ${CMAKE_SOURCE_DIR}/../../target) +set(RUST_SOURCE_FILE ${CMAKE_SOURCE_DIR}/src/lib.rs) +set(RUST_BRIDGE_CPP ${CARGO_TARGET_DIR}/cxxbridge/opendal-cpp/src/lib.rs.cc) +set(RUST_LIB ${CARGO_TARGET_DIR}/debug/${CMAKE_STATIC_LIBRARY_PREFIX}opendal_cpp${CMAKE_STATIC_LIBRARY_SUFFIX}) +set(CPP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include ${CARGO_TARGET_DIR}/cxxbridge/opendal-cpp/src) +file(GLOB_RECURSE CPP_SOURCE_FILE src/*.cpp) + +add_custom_command( + OUTPUT ${RUST_BRIDGE_CPP} ${RUST_LIB} + COMMAND cargo build --manifest-path ${CARGO_MANIFEST} + DEPENDS ${RUST_SOURCE_FILE} + USES_TERMINAL + COMMENT "Running cargo..." +) + +add_library(opendal_cpp STATIC ${CPP_SOURCE_FILE} ${RUST_BRIDGE_CPP}) +target_include_directories(opendal_cpp PUBLIC ${CPP_INCLUDE_DIR}) +target_link_libraries(opendal_cpp PUBLIC ${RUST_LIB}) +set_target_properties(opendal_cpp + PROPERTIES ADDITIONAL_CLEAN_FILES ${CARGO_TARGET_DIR} +) + +# Platform-specific test configuration +if(WIN32) + target_link_libraries(opendal_cpp userenv ws2_32 bcrypt) + set_target_properties( + opendal_cpp + PROPERTIES + MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR} + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR} + ) +endif() + +# Tests +enable_testing() +find_package(GTest REQUIRED) +file(GLOB_RECURSE TEST_SOURCE_FILE tests/*.cpp) +add_executable(opendal_cpp_test ${TEST_SOURCE_FILE}) +target_include_directories(opendal_cpp_test PUBLIC ${CPP_INCLUDE_DIR} ${GTEST_INCLUDE_DIRS}) +target_link_libraries(opendal_cpp_test ${GTEST_LDFLAGS} GTest::gtest_main opendal_cpp) +target_compile_options(opendal_cpp_test PRIVATE ${GTEST_CFLAGS}) + +# Platform-specific test configuration +if(WIN32) + target_link_libraries(opendal_cpp_test userenv ws2_32 bcrypt) +endif() +if(APPLE) + target_link_libraries(opendal_cpp_test "-framework CoreFoundation -framework Security") +endif() + +include(GoogleTest) +gtest_discover_tests(opendal_cpp_test) \ No newline at end of file diff --git a/Cargo.toml b/bindings/cpp/Cargo.toml similarity index 51% copy from Cargo.toml copy to bindings/cpp/Cargo.toml index 1b891db45..1323eac83 100644 --- a/Cargo.toml +++ b/bindings/cpp/Cargo.toml @@ -15,42 +15,24 @@ # specific language governing permissions and limitations # under the License. -[profile.bench] -debug = true +[package] +name = "opendal-cpp" +publish = false +version = "0.1.0" -[workspace] -default-members = ["core"] -exclude = ["examples"] -members = [ - "core", - "core/fuzz", +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true - "bindings/c", - "bindings/nodejs", - "bindings/python", - "bindings/ruby", - "bindings/java", - "bindings/haskell", - "bindings/lua", - "bindings/dotnet", - "bindings/ocaml", - "bindings/php", +[lib] +crate-type = ["staticlib"] - "bin/oli", - "bin/oay", +[dependencies] +opendal.workspace = true +cxx = "1.0" - "integrations/object_store", -] -resolver = "2" - -[workspace.package] -authors = ["OpenDAL Contributors <[email protected]>"] -edition = "2021" -homepage = "https://opendal.apache.org/" -license = "Apache-2.0" -repository = "https://github.com/apache/incubator-opendal" -rust-version = "1.65" -version = "0.39.0" - -[workspace.dependencies] -opendal = { version = "0.39", path = "core" } +[build-dependencies] +cxx-build = "1.0" diff --git a/bindings/cpp/README.md b/bindings/cpp/README.md new file mode 100644 index 000000000..20c0797fc --- /dev/null +++ b/bindings/cpp/README.md @@ -0,0 +1,36 @@ +# OpenDAL CPP Binding (WIP) + + + +## Example + +```cpp +#include "opendal.hpp" +#include <vector> + +int main() { + auto op = opendal::Operator("memory"); + std::vector<uint8_t> data = {1, 2, 3, 4, 5}; + op.write("test", data); + auto result = op.read("test"); // result == data +} +``` + +## Build + +```bash +mkdir build + +# Add -DCMAKE_EXPORT_COMPILE_COMMANDS=1 to generate compile_commands.json for clangd +cmake -DCMAKE_BUILD_TYPE=Debug -GNinja .. + +ninja +``` + +## Test + +You should build the project first. Then run: + +```bash +ninja test +``` \ No newline at end of file diff --git a/bindings/cpp/build.rs b/bindings/cpp/build.rs new file mode 100644 index 000000000..7d5d10f7b --- /dev/null +++ b/bindings/cpp/build.rs @@ -0,0 +1,22 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +fn main() { + let _ = cxx_build::bridge("src/lib.rs"); + + println!("cargo:rerun-if-changed=src/lib.rs"); +} diff --git a/bindings/cpp/include/opendal.hpp b/bindings/cpp/include/opendal.hpp new file mode 100644 index 000000000..4a5582140 --- /dev/null +++ b/bindings/cpp/include/opendal.hpp @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once +#include "lib.rs.h" + +#include <optional> +#include <string> +#include <string_view> +#include <unordered_map> +#include <vector> + +namespace opendal { + +class Operator : std::enable_shared_from_this<Operator> { +public: + Operator() = default; + Operator(std::string_view scheme, + const std::unordered_map<std::string, std::string> &config = {}); + + // Disable copy and assign + Operator(const Operator &) = delete; + Operator &operator=(const Operator &) = delete; + + // Enable move + Operator(Operator &&) = default; + Operator &operator=(Operator &&) = default; + ~Operator() = default; + + bool available() const; + std::vector<uint8_t> read(std::string_view path); + void write(std::string_view path, const std::vector<uint8_t> &data); + +private: + std::optional<rust::Box<opendal::ffi::Operator>> operator_; +}; + +} // namespace opendal \ No newline at end of file diff --git a/bindings/cpp/src/lib.rs b/bindings/cpp/src/lib.rs new file mode 100644 index 000000000..d574fe439 --- /dev/null +++ b/bindings/cpp/src/lib.rs @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use opendal as od; +use std::{collections::HashMap, str::FromStr}; + +#[cxx::bridge(namespace = "opendal::ffi")] +mod ffi { + struct HashMapValue { + key: String, + value: String, + } + + extern "Rust" { + type Operator; + + fn new_operator(scheme: &str, configs: Vec<HashMapValue>) -> Box<Operator>; + fn read(&self, path: &str) -> Vec<u8>; + fn write(&self, path: &str, bs: &[u8]); + } +} + +struct Operator(od::BlockingOperator); + +fn new_operator(scheme: &str, configs: Vec<ffi::HashMapValue>) -> Box<Operator> { + let scheme = od::Scheme::from_str(scheme).unwrap(); + + let map = configs + .into_iter() + .map(|value| (value.key, value.value)) + .collect::<HashMap<_, _>>(); + + Box::new(Operator( + od::Operator::via_map(scheme, map).unwrap().blocking(), + )) +} + +impl Operator { + fn read(&self, path: &str) -> Vec<u8> { + self.0.read(path).unwrap() + } + + fn write(&self, path: &str, bs: &[u8]) { + self.0.write(path, bs.to_owned()).unwrap() + } +} diff --git a/bindings/cpp/src/opendal.cpp b/bindings/cpp/src/opendal.cpp new file mode 100644 index 000000000..d45c12ea0 --- /dev/null +++ b/bindings/cpp/src/opendal.cpp @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "opendal.hpp" +#include "lib.rs.h" + +using namespace opendal; + +Operator::Operator(std::string_view scheme, + const std::unordered_map<std::string, std::string> &config) { + auto rust_map = rust::Vec<ffi::HashMapValue>(); + rust_map.reserve(config.size()); + for (const auto &[k, v] : config) { + rust_map.push_back(ffi::HashMapValue{ + rust::String(k.data()), + rust::String(v.data()), + }); + } + + operator_ = opendal::ffi::new_operator(rust::Str(scheme.data()), rust_map); +} + +bool Operator::available() const { return operator_.has_value(); } + +std::vector<uint8_t> Operator::read(std::string_view path) { + auto res = operator_.value()->read(rust::Str(path.data())); + return std::vector<uint8_t>(res.data(), res.data() + res.size()); +} + +void Operator::write(std::string_view path, const std::vector<uint8_t> &data) { + operator_.value()->write( + rust::Str(path.data()), + rust::Slice<const uint8_t>(data.data(), data.size())); +} \ No newline at end of file diff --git a/bindings/cpp/tests/basic_test.cpp b/bindings/cpp/tests/basic_test.cpp new file mode 100644 index 000000000..32811b75f --- /dev/null +++ b/bindings/cpp/tests/basic_test.cpp @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "opendal.hpp" +#include "gtest/gtest.h" +#include <string> +#include <unordered_map> + +class OpendalTest : public ::testing::Test { +protected: + opendal::Operator op; + + std::string scheme; + std::unordered_map<std::string, std::string> config; + + void SetUp() override { + this->scheme = "memory"; + op = opendal::Operator(this->scheme, this->config); + + EXPECT_TRUE(this->op.available()); + } +}; + +// Scenario: OpenDAL Blocking Operations +TEST_F(OpendalTest, BasicTest) { + std::string path = "test"; + std::vector<uint8_t> data = {1, 2, 3, 4, 5}; + + op.write("test", data); + + auto res = op.read("test"); + EXPECT_EQ(res, data); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}
