This is an automated email from the ASF dual-hosted git repository. xuanwo pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push: new d7580b462 feat(bindings/cpp): remove Boost dependency (#6376) d7580b462 is described below commit d7580b462fc6666ae87725094ad50481199b5be2 Author: Jack Drogon <jack.xsuper...@gmail.com> AuthorDate: Fri Jul 11 15:40:25 2025 +0800 feat(bindings/cpp): remove Boost dependency (#6376) - Remove Boost dependency from CMake configuration and documentation - Replace Boost.DateTime with std::chrono for timestamp handling - Replace Boost.IOStreams with custom std::istream implementation for ReaderStream - Update all header includes and modernize C++ usage Signed-off-by: Jack Drogon <jack.xsuper...@gmail.com> --- bindings/cpp/CMakeLists.txt | 17 +---- bindings/cpp/CONTRIBUTING.md | 2 - bindings/cpp/include/async_defs.hpp | 6 +- bindings/cpp/include/data_structure.hpp | 5 +- bindings/cpp/include/opendal.hpp | 110 ++++++++++++++++++++++++++------ bindings/cpp/include/opendal_async.hpp | 19 +++--- bindings/cpp/src/operator.cpp | 29 ++++++++- 7 files changed, 135 insertions(+), 53 deletions(-) diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index 377139de0..001e86043 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -27,7 +27,6 @@ project(opendal-cpp LANGUAGES CXX) include(FetchContent) set(OPENDAL_GOOGLETEST_VERSION 1.15.2 CACHE STRING "version of GoogleTest, 'external' to fallback to find_package()") -set(OPENDAL_BOOST_VERSION 1.86.0 CACHE STRING "version of Boost, 'external' to fallback to find_package()") set(OPENDAL_CPPCORO_VERSION a4ef65281814b18fdd1ac5457d3e219347ec6cb8 CACHE STRING "version of cppcoro") if (NOT CMAKE_BUILD_TYPE) @@ -135,20 +134,6 @@ add_custom_target(cargo_build COMMENT "Running cargo..." ) -if(OPENDAL_BOOST_VERSION STREQUAL "external") - find_package(Boost REQUIRED COMPONENTS date_time iostreams) -else() - # fetch Boost - FetchContent_Declare( - Boost - URL https://github.com/boostorg/boost/releases/download/boost-${OPENDAL_BOOST_VERSION}/boost-${OPENDAL_BOOST_VERSION}-cmake.zip - ) - - set(BOOST_INCLUDE_LIBRARIES date_time iostreams system) - set(BOOST_ENABLE_CMAKE ON) - FetchContent_MakeAvailable(Boost) -endif() - add_library(opendal_cpp STATIC ${CPP_SOURCE_FILE} ${RUST_BRIDGE_CPP}) target_sources(opendal_cpp PUBLIC ${CPP_HEADER_FILE}) target_sources(opendal_cpp PRIVATE ${RUST_HEADER_FILE}) @@ -157,7 +142,7 @@ if (OPENDAL_ENABLE_ASYNC) target_include_directories(opendal_cpp PUBLIC ${CARGO_TARGET_DIR}/cxxbridge) target_compile_options(opendal_cpp PUBLIC -include ${PROJECT_SOURCE_DIR}/include/async_defs.hpp) endif() -target_link_libraries(opendal_cpp PUBLIC ${RUST_LIB} Boost::date_time Boost::iostreams) +target_link_libraries(opendal_cpp PUBLIC ${RUST_LIB}) target_link_libraries(opendal_cpp PRIVATE ${CMAKE_DL_LIBS}) set_target_properties(opendal_cpp PROPERTIES ADDITIONAL_CLEAN_FILES ${CARGO_TARGET_DIR} diff --git a/bindings/cpp/CONTRIBUTING.md b/bindings/cpp/CONTRIBUTING.md index cc06ed1f7..2a74c34a0 100644 --- a/bindings/cpp/CONTRIBUTING.md +++ b/bindings/cpp/CONTRIBUTING.md @@ -32,8 +32,6 @@ To build OpenDAL C++ binding, the following is all you need: - **GTest(Google Test)**. It is used to run the tests. You do NOT need to build it manually. -- **Boost**. It is one dependency of this library. You do NOT need to build it manually. - - **Doxygen**. It is used to generate the documentation. To see how to build, check [here](https://www.doxygen.nl/manual/install.html). - **Graphviz**. It is used to generate the documentation with graphs. To see how to build, check [here](https://graphviz.org/download/). diff --git a/bindings/cpp/include/async_defs.hpp b/bindings/cpp/include/async_defs.hpp index 3b2e6c193..f5780fd55 100644 --- a/bindings/cpp/include/async_defs.hpp +++ b/bindings/cpp/include/async_defs.hpp @@ -24,8 +24,10 @@ CXXASYNC_DEFINE_FUTURE(rust::Vec<uint8_t>, opendal, ffi, async, RustFutureRead); CXXASYNC_DEFINE_FUTURE(void, opendal, ffi, async, RustFutureWrite); -CXXASYNC_DEFINE_FUTURE(rust::Vec<rust::String>, opendal, ffi, async, RustFutureList); +CXXASYNC_DEFINE_FUTURE(rust::Vec<rust::String>, opendal, ffi, async, + RustFutureList); CXXASYNC_DEFINE_FUTURE(bool, opendal, ffi, async, RustFutureBool); CXXASYNC_DEFINE_FUTURE(size_t, opendal, ffi, async, RustFutureReaderId); CXXASYNC_DEFINE_FUTURE(size_t, opendal, ffi, async, RustFutureListerId); -CXXASYNC_DEFINE_FUTURE(rust::String, opendal, ffi, async, RustFutureEntryOption); +CXXASYNC_DEFINE_FUTURE(rust::String, opendal, ffi, async, + RustFutureEntryOption); diff --git a/bindings/cpp/include/data_structure.hpp b/bindings/cpp/include/data_structure.hpp index 443eaa626..984bd2ac3 100644 --- a/bindings/cpp/include/data_structure.hpp +++ b/bindings/cpp/include/data_structure.hpp @@ -19,12 +19,11 @@ #pragma once +#include <chrono> #include <cstdint> #include <optional> #include <string> -#include "boost/date_time/posix_time/ptime.hpp" - namespace opendal { /** @@ -50,7 +49,7 @@ class Metadata { std::optional<std::string> content_md5; std::optional<std::string> content_type; std::optional<std::string> etag; - std::optional<boost::posix_time::ptime> last_modified; + std::optional<std::chrono::system_clock::time_point> last_modified; }; /** diff --git a/bindings/cpp/include/opendal.hpp b/bindings/cpp/include/opendal.hpp index 78c1e5f40..b9f13ed69 100644 --- a/bindings/cpp/include/opendal.hpp +++ b/bindings/cpp/include/opendal.hpp @@ -19,13 +19,14 @@ #pragma once +#include <cstring> +#include <iostream> +#include <memory> #include <optional> #include <string> #include <unordered_map> #include <vector> -#include "boost/iostreams/concepts.hpp" -#include "boost/iostreams/stream.hpp" #include "data_structure.hpp" namespace opendal { @@ -174,14 +175,13 @@ class Operator { /** * @class Reader * @brief Reader is designed to read data from the operator. - * @details It provides basic read and seek operations. If you want to use it - * like a stream, you can use `ReaderStream` instead. + * @details It provides basic read and seek operations with a stream-like + * interface. * @code{.cpp} * opendal::ReaderStream stream(operator.reader("path")); * @endcode */ -class Reader - : public boost::iostreams::device<boost::iostreams::input_seekable> { +class Reader { public: Reader(Reader &&other) noexcept; @@ -201,26 +201,98 @@ class Reader ffi::Reader *reader_{nullptr}; }; -// Boost IOStreams requires it to be copyable. So we need to use -// `reference_wrapper` in ReaderStream. More details can be seen at -// https://lists.boost.org/Archives/boost/2005/10/95939.php - /** * @class ReaderStream - * @brief ReaderStream is a stream wrapper of Reader which can provide - * `iostream` interface. It will keep a Reader inside so that you can ignore the - * lifetime of original Reader. + * @brief ReaderStream is a stream wrapper of Reader which provides + * `iostream` interface. It wraps the Reader to provide standard stream + * operations. */ -class ReaderStream - : public boost::iostreams::stream<boost::reference_wrapper<Reader>> { +class ReaderStream : public std::istream { public: + class ReaderStreamBuf : public std::streambuf { + public: + ReaderStreamBuf(Reader &&reader) + : reader_(std::move(reader)), buffer_start_pos_(0) { + setg(buffer_, buffer_, buffer_); + } + + protected: + std::streamsize xsgetn(char *s, std::streamsize count) override { + std::streamsize total_read = 0; + + while (total_read < count) { + if (gptr() < egptr()) { + std::streamsize available = egptr() - gptr(); + std::streamsize to_copy = std::min(available, count - total_read); + std::memcpy(s + total_read, gptr(), to_copy); + gbump(to_copy); + total_read += to_copy; + continue; + } + + if (underflow() == traits_type::eof()) break; + } + + return total_read; + } + + int_type underflow() override { + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + // Update the buffer start position to current reader position + buffer_start_pos_ += (egptr() - eback()); + + std::streamsize n = reader_.read(buffer_, sizeof(buffer_)); + if (n <= 0) { + return traits_type::eof(); + } + + setg(buffer_, buffer_, buffer_ + n); + return traits_type::to_int_type(*gptr()); + } + + int_type uflow() override { + int_type result = underflow(); + if (result != traits_type::eof()) { + gbump(1); + } + return result; + } + + std::streampos seekoff(std::streamoff off, std::ios_base::seekdir dir, + std::ios_base::openmode which) override { + if (dir == std::ios_base::cur && off == 0) { + // tellg() case - return current position + return buffer_start_pos_ + (gptr() - eback()); + } + + // Actual seek operation + std::streampos new_pos = reader_.seek(off, dir); + if (new_pos != std::streampos(-1)) { + buffer_start_pos_ = new_pos; + setg(buffer_, buffer_, buffer_); + } + return new_pos; + } + + std::streampos seekpos(std::streampos pos, + std::ios_base::openmode which) override { + return seekoff(pos, std::ios_base::beg, which); + } + + private: + Reader reader_; + char buffer_[8192]; + std::streampos buffer_start_pos_; + }; + ReaderStream(Reader &&reader) - : boost::iostreams::stream<boost::reference_wrapper<Reader>>( - boost::ref(reader_)), - reader_(std::move(reader)) {} + : std::istream(&buf_), buf_(std::move(reader)) {} private: - Reader reader_; + ReaderStreamBuf buf_; }; /** diff --git a/bindings/cpp/include/opendal_async.hpp b/bindings/cpp/include/opendal_async.hpp index 1f5deca10..435aaea80 100644 --- a/bindings/cpp/include/opendal_async.hpp +++ b/bindings/cpp/include/opendal_async.hpp @@ -84,7 +84,8 @@ class Operator { /** * @class Reader - * @brief Async Reader is designed to read data from a specific path in an asynchronous manner. + * @brief Async Reader is designed to read data from a specific path in an + * asynchronous manner. * @details It provides streaming read operations with range support. */ class Reader { @@ -102,7 +103,7 @@ class Reader { explicit Reader(size_t reader_id) noexcept; using ReadFuture = opendal::ffi::async::RustFutureRead; - + /** * @brief Read data from the specified range * @param start Start offset in bytes @@ -113,7 +114,7 @@ class Reader { private: friend class Operator; - + void destroy() noexcept; size_t reader_id_{0}; @@ -121,12 +122,13 @@ class Reader { /** * @class Lister - * @brief Async Lister is designed to list entries at a specified path in an asynchronous manner. + * @brief Async Lister is designed to list entries at a specified path in an + * asynchronous manner. * @details It provides streaming iteration over directory entries. */ class Lister { public: - // Disable copy and assign + // Disable copy and assign Lister(const Lister &) = delete; Lister &operator=(const Lister &) = delete; @@ -139,16 +141,17 @@ class Lister { explicit Lister(size_t lister_id) noexcept; using NextFuture = opendal::ffi::async::RustFutureEntryOption; - + /** * @brief Get the next entry in the listing - * @return Future that resolves to the next entry path, or empty string if no more entries + * @return Future that resolves to the next entry path, or empty string if no + * more entries */ NextFuture next(); private: friend class Operator; - + void destroy() noexcept; size_t lister_id_{0}; diff --git a/bindings/cpp/src/operator.cpp b/bindings/cpp/src/operator.cpp index fde148cae..1276df2f5 100644 --- a/bindings/cpp/src/operator.cpp +++ b/bindings/cpp/src/operator.cpp @@ -17,7 +17,10 @@ * under the License. */ -#include "boost/date_time/posix_time/time_parsers.hpp" +#include <chrono> +#include <cstdio> +#include <ctime> + #include "lib.rs.h" #include "opendal.hpp" #include "utils/ffi_converter.hpp" @@ -47,8 +50,28 @@ Metadata parse_meta_data(ffi::Metadata &&meta) { auto last_modified_str = parse_optional_string(std::move(meta.last_modified)); if (last_modified_str.has_value()) { - metadata.last_modified = - boost::posix_time::from_iso_string(last_modified_str.value()); + // Parse ISO 8601 string to time_point using strptime to avoid locale lock + std::tm tm = {}; + const char *str = last_modified_str.value().c_str(); + + // Parse ISO 8601 format: YYYY-MM-DDTHH:MM:SS + int year, month, day, hour, minute, second; + if (sscanf(str, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute, + &second) == 6) { + tm.tm_year = year - 1900; // years since 1900 + tm.tm_mon = month - 1; // months since January (0-11) + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = minute; + tm.tm_sec = second; + tm.tm_isdst = -1; // let mktime determine DST + + std::time_t time_t_value = std::mktime(&tm); + if (time_t_value != -1) { + metadata.last_modified = + std::chrono::system_clock::from_time_t(time_t_value); + } + } } return metadata;