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;

Reply via email to