From 2de12fe666b29a51376d9a648efbca9a841209ca Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 11:08:29 -0500 Subject: [PATCH 1/8] switch to c++17!! --- CMakeLists.txt | 18 +- conanfile.py | 2 +- examples/CMakeLists.txt | 4 +- examples/trxinfo.cpp | 26 +- include/trx/compat.h | 96 ------ include/trx/dirent.h | 88 ----- include/trx/filesystem.h | 584 ---------------------------------- include/trx/trx.h | 8 +- include/trx/trx.tpp | 98 ++++-- src/trx.cpp | 303 +++++++++--------- test_package/CMakeLists.txt | 4 +- tests/CMakeLists.txt | 10 +- tests/test_trx_filesystem.cpp | 15 +- tests/test_trx_io.cpp | 70 ++-- tests/test_trx_mmap.cpp | 4 +- tests/test_trx_trxfile.cpp | 4 +- 16 files changed, 290 insertions(+), 1044 deletions(-) delete mode 100644 include/trx/compat.h delete mode 100644 include/trx/dirent.h delete mode 100644 include/trx/filesystem.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 897f68a..3e38573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.16) cmake_policy(SET CMP0074 NEW) cmake_policy(SET CMP0079 NEW) @@ -8,12 +8,14 @@ include(GNUInstallDirs) include(CMakePackageConfigHelpers) include(FetchContent) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Debug) endif() -option(TRX_USE_CONAN "Should Conan package manager be used?" ON) +option(TRX_USE_CONAN "Should Conan package manager be used?" OFF) option(TRX_BUILD_TESTS "Build trx tests" OFF) option(TRX_BUILD_EXAMPLES "Build trx example commandline programs" ON) @@ -57,6 +59,8 @@ add_library(trx ) add_library(trx-cpp::trx ALIAS trx) +target_compile_features(trx PUBLIC cxx_std_17) + if(TRX_USE_CONAN AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ConanSetup.cmake") include(cmake/ConanSetup.cmake) elseif(TRX_USE_CONAN) @@ -110,11 +114,11 @@ endif() if(TRX_BUILD_EXAMPLES) FetchContent_Declare( - CLI11 - GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git - GIT_TAG v2.4.2 + cxxopts + GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git + GIT_TAG v3.2.0 ) - FetchContent_MakeAvailable(CLI11) + FetchContent_MakeAvailable(cxxopts) add_subdirectory(examples) endif() diff --git a/conanfile.py b/conanfile.py index d86e431..41243c3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -52,7 +52,7 @@ def layout(self): def generate(self): tc = CMakeToolchain(self) - tc.variables["CMAKE_CXX_STANDARD"] = 11 + tc.variables["CMAKE_CXX_STANDARD"] = 17 tc.variables["CMAKE_CXX_STANDARD_REQUIRED"] = "ON" tc.variables["CMAKE_CXX_EXTENSIONS"] = "OFF" tc.generate() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index abdcd82..cae2ed3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(trxinfo trxinfo.cpp) -target_link_libraries(trxinfo PRIVATE trx CLI11::CLI11) -target_compile_features(trxinfo PRIVATE cxx_std_11) +target_link_libraries(trxinfo PRIVATE trx cxxopts::cxxopts) +target_compile_features(trxinfo PRIVATE cxx_std_17) install(TARGETS trxinfo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/examples/trxinfo.cpp b/examples/trxinfo.cpp index d1d7677..532233a 100644 --- a/examples/trxinfo.cpp +++ b/examples/trxinfo.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include "cli_colors.h" @@ -210,20 +210,36 @@ struct ReaderPrinter int main(int argc, char **argv) { - CLI::App app{"Print information about a TRX file or directory."}; std::string path; bool show_stats = false; - app.add_option("path", path, "Path to TRX file or directory")->required(); - app.add_flag("--stats", show_stats, "Compute Min/Mean/Max streamline lengths"); + cxxopts::Options options("trxinfo", "Print information about a TRX file or directory."); + options.add_options()("stats", "Compute Min/Mean/Max streamline lengths", + cxxopts::value(show_stats)->default_value("false"))( + "path", "Path to TRX file or directory", cxxopts::value(path)); + options.parse_positional({"path"}); - CLI11_PARSE(app, argc, argv); try { + auto result = options.parse(argc, argv); + if (!result.count("path")) + { + std::cerr << options.help() << "\n"; + return 1; + } + path = result["path"].as(); + show_stats = result["stats"].as(); + const bool is_dir = trxmmap::is_trx_directory(path); ReaderPrinter printer{path, is_dir, show_stats}; return trxmmap::with_trx_reader(path, printer); } + catch (const cxxopts::exceptions::exception &e) + { + std::cerr << "trxinfo: " << e.what() << "\n"; + std::cerr << options.help() << "\n"; + return 1; + } catch (const std::exception &e) { std::cerr << "trxinfo: " << e.what() << "\n"; diff --git a/include/trx/compat.h b/include/trx/compat.h deleted file mode 100644 index 85c316a..0000000 --- a/include/trx/compat.h +++ /dev/null @@ -1,96 +0,0 @@ -#ifndef TRX_COMPAT_H -#define TRX_COMPAT_H - -#include - -#if defined(_WIN32) || defined(_WIN64) -#include -#include -#include -#include -#include -#include -#include - -#ifndef S_IRWXU -#define S_IRWXU (_S_IREAD | _S_IWRITE) -#endif -#ifndef PATH_MAX -#define PATH_MAX MAX_PATH -#endif -#ifndef S_ISDIR -#define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR) -#endif - -inline int trx_mkdir(const char *path, int) -{ - return _mkdir(path); -} - -inline int trx_unlink(const char *path) -{ - return _unlink(path); -} - -inline int trx_rmdir(const char *path) -{ - return _rmdir(path); -} - -inline int trx_open(const char *path, int oflag, int pmode = 0) -{ - return _open(path, oflag, pmode); -} - -inline int trx_read(int fd, void *buffer, unsigned int count) -{ - return _read(fd, buffer, count); -} - -inline int trx_write(int fd, const void *buffer, unsigned int count) -{ - return _write(fd, buffer, count); -} - -inline int trx_close(int fd) -{ - return _close(fd); -} - -inline char *trx_realpath(const char *path, char *resolved) -{ - return _fullpath(resolved, path, PATH_MAX); -} - -#define mkdir(path, mode) trx_mkdir(path, mode) -#define unlink(path) trx_unlink(path) -#define rmdir(path) trx_rmdir(path) -#define realpath(path, resolved) trx_realpath(path, resolved) - -#else -#include -#include -#include - -inline int trx_open(const char *path, int oflag, int pmode = 0) -{ - return ::open(path, oflag, pmode); -} - -inline int trx_read(int fd, void *buffer, unsigned int count) -{ - return ::read(fd, buffer, count); -} - -inline int trx_write(int fd, const void *buffer, unsigned int count) -{ - return ::write(fd, buffer, count); -} - -inline int trx_close(int fd) -{ - return ::close(fd); -} -#endif - -#endif diff --git a/include/trx/dirent.h b/include/trx/dirent.h deleted file mode 100644 index a1d208f..0000000 --- a/include/trx/dirent.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef TRX_DIRENT_H -#define TRX_DIRENT_H - -#if defined(_WIN32) || defined(_WIN64) -#include -#include -#include - -#ifndef DT_UNKNOWN -#define DT_UNKNOWN 0 -#endif -#ifndef DT_DIR -#define DT_DIR 4 -#endif -#ifndef DT_REG -#define DT_REG 8 -#endif - -struct dirent -{ - char d_name[MAX_PATH]; - unsigned char d_type; -}; - -struct DIR -{ - HANDLE handle; - WIN32_FIND_DATAA data; - bool first; - std::string pattern; - dirent entry; -}; - -inline DIR *opendir(const char *name) -{ - if (!name || !*name) - return nullptr; - DIR *dir = new DIR(); - dir->first = true; - dir->pattern = std::string(name); - if (!dir->pattern.empty() && dir->pattern.back() != '\\' && dir->pattern.back() != '/') - dir->pattern += "\\*"; - else - dir->pattern += "*"; - dir->handle = FindFirstFileA(dir->pattern.c_str(), &dir->data); - if (dir->handle == INVALID_HANDLE_VALUE) - { - delete dir; - return nullptr; - } - return dir; -} - -inline dirent *readdir(DIR *dirp) -{ - if (!dirp) - return nullptr; - if (dirp->first) - { - dirp->first = false; - } - else - { - if (!FindNextFileA(dirp->handle, &dirp->data)) - return nullptr; - } - const char *name = dirp->data.cFileName; - strncpy_s(dirp->entry.d_name, name, MAX_PATH - 1); - dirp->entry.d_name[MAX_PATH - 1] = '\0'; - dirp->entry.d_type = (dirp->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG; - return &dirp->entry; -} - -inline int closedir(DIR *dirp) -{ - if (!dirp) - return -1; - if (dirp->handle != INVALID_HANDLE_VALUE) - FindClose(dirp->handle); - delete dirp; - return 0; -} - -#else -#include -#endif - -#endif diff --git a/include/trx/filesystem.h b/include/trx/filesystem.h deleted file mode 100644 index ff810e8..0000000 --- a/include/trx/filesystem.h +++ /dev/null @@ -1,584 +0,0 @@ -#ifndef TRX_FILESYSTEM_H -#define TRX_FILESYSTEM_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(_WIN32) || defined(_WIN64) -#include -#include -#include -#include -#include -#ifndef S_ISREG -#define S_ISREG(m) (((m)&_S_IFREG) == _S_IFREG) -#endif -#ifndef S_ISDIR -#define S_ISDIR(m) (((m)&_S_IFDIR) == _S_IFDIR) -#endif -#else -#include -#endif - -namespace trx -{ - namespace fs - { - enum class perms : unsigned - { - none = 0, - owner_read = 0400, - owner_write = 0200, - owner_exec = 0100, - group_read = 0040, - group_write = 0020, - group_exec = 0010, - others_read = 0004, - others_write = 0002, - others_exec = 0001, - all = 0777 - }; - - inline unsigned _perm_mask(perms p) - { - return static_cast(p); - } - } // namespace fs -} // namespace trx - -namespace trx -{ - namespace fs - { - inline bool _is_separator(char c) - { - return c == '/' || c == '\\'; - } - - inline char _preferred_separator() - { -#if defined(_WIN32) || defined(_WIN64) - return '\\'; -#else - return '/'; -#endif - } - - class path - { - public: - path() {} - path(const std::string &p) : path_(p) {} - path(const char *p) : path_(p ? p : "") {} - - std::string string() const { return path_; } - const char *c_str() const { return path_.c_str(); } - bool empty() const { return path_.empty(); } - void clear() { path_.clear(); } - - bool is_absolute() const - { -#if defined(_WIN32) || defined(_WIN64) - if (path_.size() >= 2 && std::isalpha(path_[0]) && path_[1] == ':') - return (path_.size() >= 3 && _is_separator(path_[2])); - if (path_.size() >= 2 && _is_separator(path_[0]) && _is_separator(path_[1])) - return true; - return (!path_.empty() && _is_separator(path_[0])); -#else - return (!path_.empty() && path_[0] == '/'); -#endif - } - - bool has_parent_path() const - { - return !parent_path().empty(); - } - - path parent_path() const - { - if (path_.empty()) - return path(); - - size_t end = path_.size(); - while (end > 0 && _is_separator(path_[end - 1])) - --end; - if (end == 0) - return path(); - - size_t pos = path_.find_last_of("/\\", end - 1); - if (pos == std::string::npos) - return path(); - if (pos == 0) - return path(std::string(1, _preferred_separator())); -#if defined(_WIN32) || defined(_WIN64) - if (pos == 2 && std::isalpha(path_[0]) && path_[1] == ':') - return path(path_.substr(0, 3)); -#endif - return path(path_.substr(0, pos)); - } - - path lexically_normal() const - { - if (path_.empty()) - return path(); - - std::string root; - size_t start = 0; -#if defined(_WIN32) || defined(_WIN64) - if (path_.size() >= 2 && std::isalpha(path_[0]) && path_[1] == ':') - { - root = path_.substr(0, 2); - start = 2; - if (path_.size() >= 3 && _is_separator(path_[2])) - { - root += _preferred_separator(); - start = 3; - } - } - else if (path_.size() >= 2 && _is_separator(path_[0]) && _is_separator(path_[1])) - { - root = std::string(2, _preferred_separator()); - start = 2; - } - else if (!path_.empty() && _is_separator(path_[0])) - { - root = std::string(1, _preferred_separator()); - start = 1; - } -#else - if (!path_.empty() && path_[0] == '/') - { - root = "/"; - start = 1; - } -#endif - - std::vector parts; - size_t i = start; - while (i < path_.size()) - { - while (i < path_.size() && _is_separator(path_[i])) - ++i; - if (i >= path_.size()) - break; - size_t j = i; - while (j < path_.size() && !_is_separator(path_[j])) - ++j; - std::string part = path_.substr(i, j - i); - if (part == ".") - { - // skip - } - else if (part == "..") - { - if (!parts.empty() && parts.back() != "..") - { - parts.pop_back(); - } - else if (root.empty()) - { - parts.push_back(".."); - } - } - else - { - parts.push_back(part); - } - i = j; - } - - std::string out = root; - for (size_t idx = 0; idx < parts.size(); ++idx) - { - if (!out.empty() && !_is_separator(out[out.size() - 1])) - out += _preferred_separator(); - out += parts[idx]; - } - - if (out.empty() && root.empty()) - out = "."; - - return path(out); - } - - private: - std::string path_; - }; - - inline path operator/(const path &lhs, const path &rhs) - { - if (rhs.is_absolute()) - return rhs; - if (lhs.string().empty()) - return rhs; - std::string out = lhs.string(); - if (!_is_separator(out[out.size() - 1])) - out += _preferred_separator(); - out += rhs.string(); - return path(out); - } - - inline path operator/(const path &lhs, const char *rhs) - { - return lhs / path(rhs); - } - - inline path operator/(const path &lhs, const std::string &rhs) - { - return lhs / path(rhs); - } - - inline path operator/(const std::string &lhs, const path &rhs) - { - return path(lhs) / rhs; - } - - inline path operator/(const char *lhs, const path &rhs) - { - return path(lhs) / rhs; - } - - inline bool exists(const path &p, std::error_code &ec) - { - struct stat buf; - if (stat(p.c_str(), &buf) == 0) - { - ec.clear(); - return true; - } - if (errno == ENOENT) - { - ec.clear(); - return false; - } - ec = std::error_code(errno, std::generic_category()); - return false; - } - - inline bool exists(const path &p) - { - std::error_code ec; - bool ok = exists(p, ec); - if (ec) - throw std::runtime_error(ec.message()); - return ok; - } - - inline bool is_directory(const path &p, std::error_code &ec) - { - struct stat buf; - if (stat(p.c_str(), &buf) == 0) - { - ec.clear(); - return S_ISDIR(buf.st_mode); - } - if (errno == ENOENT) - { - ec.clear(); - return false; - } - ec = std::error_code(errno, std::generic_category()); - return false; - } - - inline bool create_directory(const path &p, std::error_code &ec) - { -#if defined(_WIN32) || defined(_WIN64) - int rc = _mkdir(p.c_str()); -#else - int rc = mkdir(p.c_str(), 0777); -#endif - if (rc == 0) - { - ec.clear(); - return true; - } - if (errno == EEXIST) - { - ec.clear(); - return false; - } - ec = std::error_code(errno, std::generic_category()); - return false; - } - - inline bool create_directories(const path &p, std::error_code &ec) - { - if (p.empty()) - { - ec.clear(); - return false; - } - - if (exists(p, ec)) - { - if (ec) - return false; - if (is_directory(p, ec)) - return false; - ec = std::make_error_code(std::errc::not_a_directory); - return false; - } - - path parent = p.parent_path(); - if (!parent.empty() && parent.string() != p.string()) - { - create_directories(parent, ec); - if (ec) - return false; - } - - return create_directory(p, ec); - } - - inline path temp_directory_path(std::error_code &ec) - { - const char *candidates[] = {std::getenv("TMPDIR"), std::getenv("TEMP"), std::getenv("TMP")}; - for (const char *candidate : candidates) - { - if (!candidate || std::string(candidate).empty()) - continue; - path p(candidate); - if (is_directory(p, ec)) - { - ec.clear(); - return p; - } - if (ec) - ec.clear(); - } - -#if defined(_WIN32) || defined(_WIN64) - path fallback("C:\\Temp"); -#else - path fallback("/tmp"); -#endif - if (is_directory(fallback, ec)) - { - ec.clear(); - return fallback; - } - if (!ec) - ec = std::make_error_code(std::errc::no_such_file_or_directory); - return path(); - } - - inline std::uintmax_t remove_all(const path &p, std::error_code &ec) - { - struct stat buf; - if (stat(p.c_str(), &buf) != 0) - { - if (errno == ENOENT) - { - ec.clear(); - return 0; - } - ec = std::error_code(errno, std::generic_category()); - return 0; - } - - std::uintmax_t count = 0; - if (S_ISDIR(buf.st_mode)) - { - DIR *dir = opendir(p.c_str()); - if (!dir) - { - ec = std::error_code(errno, std::generic_category()); - return count; - } - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) - { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - path child = path(p.string()) / entry->d_name; - count += remove_all(child, ec); - if (ec) - { - closedir(dir); - return count; - } - } - closedir(dir); - if (rmdir(p.c_str()) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return count; - } - ++count; - } - else - { - if (unlink(p.c_str()) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return count; - } - ++count; - } - - ec.clear(); - return count; - } - - inline std::uintmax_t file_size(const path &p, std::error_code &ec) - { -#if defined(_WIN32) || defined(_WIN64) - struct _stat buf; - if (_stat(p.c_str(), &buf) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return static_cast(-1); - } -#else - struct stat buf; - if (stat(p.c_str(), &buf) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return static_cast(-1); - } -#endif - if (!S_ISREG(buf.st_mode)) - { - if (S_ISDIR(buf.st_mode)) - ec = std::make_error_code(std::errc::is_a_directory); - else - ec = std::make_error_code(std::errc::invalid_argument); - return static_cast(-1); - } - ec.clear(); - return static_cast(buf.st_size); - } - - inline std::uintmax_t file_size(const path &p) - { - std::error_code ec; - std::uintmax_t size = file_size(p, ec); - if (ec) - throw std::runtime_error(ec.message()); - return size; - } - - inline bool is_symlink(const path &p, std::error_code &ec) - { -#if defined(_WIN32) || defined(_WIN64) - DWORD attrs = GetFileAttributesA(p.c_str()); - if (attrs == INVALID_FILE_ATTRIBUTES) - { - ec = std::error_code(GetLastError(), std::system_category()); - return false; - } - ec.clear(); - return (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0; -#else - struct stat buf; - if (lstat(p.c_str(), &buf) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return false; - } - ec.clear(); - return S_ISLNK(buf.st_mode); -#endif - } - - inline bool is_symlink(const path &p) - { - std::error_code ec; - bool ok = is_symlink(p, ec); - if (ec) - throw std::runtime_error(ec.message()); - return ok; - } - - inline bool create_symlink(const path &target, const path &link, std::error_code &ec) - { -#if defined(_WIN32) || defined(_WIN64) - DWORD flags = 0; - struct _stat buf; - if (_stat(target.c_str(), &buf) == 0 && (buf.st_mode & _S_IFDIR)) - flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; - if (CreateSymbolicLinkA(link.c_str(), target.c_str(), flags) == 0) - { - ec = std::error_code(GetLastError(), std::system_category()); - return false; - } - ec.clear(); - return true; -#else - if (symlink(target.c_str(), link.c_str()) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return false; - } - ec.clear(); - return true; -#endif - } - - inline void permissions(const path &p, perms prms, std::error_code &ec) - { -#if defined(_WIN32) || defined(_WIN64) - int mode = _S_IREAD; - if ((_perm_mask(prms) & _perm_mask(perms::owner_write)) != 0) - mode |= _S_IWRITE; - if (_chmod(p.c_str(), mode) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return; - } - ec.clear(); -#else - mode_t mode = static_cast(_perm_mask(prms) & _perm_mask(perms::all)); - if (chmod(p.c_str(), mode) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return; - } - ec.clear(); -#endif - } - - inline perms permissions(const path &p, std::error_code &ec) - { -#if defined(_WIN32) || defined(_WIN64) - struct _stat buf; - if (_stat(p.c_str(), &buf) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return perms::none; - } - ec.clear(); - return static_cast(buf.st_mode & 0777); -#else - struct stat buf; - if (stat(p.c_str(), &buf) != 0) - { - ec = std::error_code(errno, std::generic_category()); - return perms::none; - } - ec.clear(); - return static_cast(buf.st_mode & 0777); -#endif - } - - inline perms permissions(const path &p) - { - std::error_code ec; - perms out = permissions(p, ec); - if (ec) - throw std::runtime_error(ec.message()); - return out; - } - } // namespace fs -} // namespace trx - -#endif diff --git a/include/trx/trx.h b/include/trx/trx.h index 92ccf1f..46604b9 100644 --- a/include/trx/trx.h +++ b/include/trx/trx.h @@ -12,8 +12,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -22,6 +21,11 @@ #include #include +namespace trx +{ + namespace fs = std::filesystem; +} + using namespace Eigen; using json = json11::Json; diff --git a/include/trx/trx.tpp b/include/trx/trx.tpp index 07fcfab..ef1c6d2 100644 --- a/include/trx/trx.tpp +++ b/include/trx/trx.tpp @@ -272,12 +272,22 @@ TrxFile
*_initialize_empty_trx(int nb_streamlines, int nb_vertices, const Tr if (init_as->data_per_vertex.size() > 0) { dpv_dirname = tmp_dir + "/dpv/"; - mkdir(dpv_dirname.c_str(), S_IRWXU); + std::error_code ec; + trx::fs::create_directories(dpv_dirname, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + dpv_dirname); + } } if (init_as->data_per_streamline.size() > 0) { dps_dirname = tmp_dir + "/dps/"; - mkdir(dps_dirname.c_str(), S_IRWXU); + std::error_code ec; + trx::fs::create_directories(dps_dirname, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + dps_dirname); + } } for (auto const &x : init_as->data_per_vertex) @@ -672,9 +682,13 @@ TrxFile
*TrxFile
::deepcopy() if (this->data_per_vertex.size() > 0) { std::string dpv_dirname = tmp_dir + SEPARATOR + "dpv" + SEPARATOR; - if (mkdir(dpv_dirname.c_str(), S_IRWXU) != 0) { - throw std::runtime_error("Could not create directory " + dpv_dirname); + std::error_code ec; + trx::fs::create_directories(dpv_dirname, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + dpv_dirname); + } } for (auto const &x : this->data_per_vertex) { @@ -689,9 +703,13 @@ TrxFile
*TrxFile
::deepcopy() if (this->data_per_streamline.size() > 0) { std::string dps_dirname = tmp_dir + SEPARATOR + "dps" + SEPARATOR; - if (mkdir(dps_dirname.c_str(), S_IRWXU) != 0) { - throw std::runtime_error("Could not create directory " + dps_dirname); + std::error_code ec; + trx::fs::create_directories(dps_dirname, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + dps_dirname); + } } for (auto const &x : this->data_per_streamline) { @@ -706,9 +724,13 @@ TrxFile
*TrxFile
::deepcopy() if (this->groups.size() > 0) { std::string groups_dirname = tmp_dir + SEPARATOR + "groups" + SEPARATOR; - if (mkdir(groups_dirname.c_str(), S_IRWXU) != 0) { - throw std::runtime_error("Could not create directory " + groups_dirname); + std::error_code ec; + trx::fs::create_directories(groups_dirname, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + groups_dirname); + } } for (auto const &x : this->groups) @@ -728,21 +750,25 @@ TrxFile
*TrxFile
::deepcopy() { std::string dpg_dirname = tmp_dir + SEPARATOR + "dpg" + SEPARATOR; std::string dpg_subdirname = dpg_dirname + x.first; - struct stat sb; - - if (stat(dpg_dirname.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) + std::error_code ec; + if (!trx::fs::exists(dpg_dirname, ec)) { - if (mkdir(dpg_dirname.c_str(), S_IRWXU) != 0) - { - throw std::runtime_error("Could not create directory " + dpg_dirname); - } + ec.clear(); + trx::fs::create_directories(dpg_dirname, ec); } - if (stat(dpg_subdirname.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) + if (ec) { - if (mkdir(dpg_subdirname.c_str(), S_IRWXU) != 0) - { - throw std::runtime_error("Could not create directory " + dpg_subdirname); - } + throw std::runtime_error("Could not create directory " + dpg_dirname); + } + ec.clear(); + if (!trx::fs::exists(dpg_subdirname, ec)) + { + ec.clear(); + trx::fs::create_directories(dpg_subdirname, ec); + } + if (ec) + { + throw std::runtime_error("Could not create directory " + dpg_subdirname); } Matrix dpg_todump = this->data_per_group[x.first][y.first]->_matrix; @@ -919,9 +945,13 @@ void TrxFile
::resize(int nb_streamlines, int nb_vertices, bool delete_dpg) if (this->groups.size() > 0) { std::string group_dir = tmp_dir + SEPARATOR + "groups" + SEPARATOR; - if (mkdir(group_dir.c_str(), S_IRWXU) != 0) { - throw std::runtime_error("Could not create directory " + group_dir); + std::error_code ec; + trx::fs::create_directories(group_dir, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + group_dir); + } } for (auto const &x : this->groups) @@ -975,19 +1005,22 @@ void TrxFile
::resize(int nb_streamlines, int nb_vertices, bool delete_dpg) { // really need to refactor all these mkdirs std::string dpg_dir = tmp_dir + SEPARATOR + "dpg" + SEPARATOR; - if (mkdir(dpg_dir.c_str(), S_IRWXU) != 0) { - throw std::runtime_error("Could not create directory " + dpg_dir); + std::error_code ec; + trx::fs::create_directories(dpg_dir, ec); + if (ec) + { + throw std::runtime_error("Could not create directory " + dpg_dir); + } } for (auto const &x : this->data_per_group) { std::string dpg_subdir = dpg_dir + x.first; - struct stat sb; - - if (stat(dpg_subdir.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) { - if (mkdir(dpg_subdir.c_str(), S_IRWXU) != 0) + std::error_code ec; + trx::fs::create_directories(dpg_subdir, ec); + if (ec) { throw std::runtime_error("Could not create directory " + dpg_subdir); } @@ -1051,10 +1084,13 @@ template TrxFile
*load_from_directory(std::string path) { std::string directory = path; - char resolved[PATH_MAX]; - if (realpath(path.c_str(), resolved) != nullptr) { - directory = resolved; + std::error_code ec; + trx::fs::path resolved = trx::fs::weakly_canonical(trx::fs::path(path), ec); + if (!ec) + { + directory = resolved.string(); + } } std::string header_name = directory + SEPARATOR + "header.json"; diff --git a/src/trx.cpp b/src/trx.cpp index b0cc9c2..d6d975b 100644 --- a/src/trx.cpp +++ b/src/trx.cpp @@ -121,73 +121,84 @@ namespace trxmmap void populate_fps(const char *name, std::map> &files_pointer_size) { - DIR *dir; - struct dirent *entry; - - if (!(dir = opendir(name))) + trx::fs::path root(name); + std::error_code ec; + if (!trx::fs::exists(root, ec) || !trx::fs::is_directory(root, ec)) + { return; - - while ((entry = readdir(dir)) != NULL) + } + ec.clear(); + for (trx::fs::recursive_directory_iterator it(root, ec), end; it != end; it.increment(ec)) { - if (entry->d_name[0] == '.') - continue; - if (strcmp(entry->d_name, "__MACOSX") == 0) - continue; - if (entry->d_type == DT_DIR) + if (ec) { - char path[1024]; - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - snprintf(path, sizeof(path), "%s/%s", name, entry->d_name); - populate_fps(path, files_pointer_size); + throw std::runtime_error("Failed to read directory: " + root.string()); } - else + const trx::fs::path entry_path = it->path(); + const std::string filename = entry_path.filename().string(); + if (!filename.empty() && filename[0] == '.') { - std::string filename(entry->d_name); - std::string root(name); - std::string elem_filename = root + SEPARATOR + filename; - std::string ext = get_ext(elem_filename); - - if (strcmp(ext.c_str(), "json") == 0) + if (it->is_directory(ec)) { - continue; + it.disable_recursion_pending(); } - - if (!_is_dtype_valid(ext)) + continue; + } + if (filename == "__MACOSX") + { + if (it->is_directory(ec)) { - throw std::invalid_argument(std::string("The dtype of ") + elem_filename + std::string(" is not supported")); + it.disable_recursion_pending(); } + continue; + } - if (strcmp(ext.c_str(), "bit") == 0) - { - ext = "bool"; - } + std::error_code entry_ec; + if (!it->is_regular_file(entry_ec)) + { + continue; + } - int dtype_size = _sizeof_dtype(ext); + std::string elem_filename = entry_path.string(); + std::string ext = get_ext(elem_filename); - struct stat sb; - unsigned long size = 0; + if (strcmp(ext.c_str(), "json") == 0) + { + continue; + } - if (stat(elem_filename.c_str(), &sb) == 0) - { - size = sb.st_size / dtype_size; - } + if (!_is_dtype_valid(ext)) + { + throw std::invalid_argument(std::string("The dtype of ") + elem_filename + std::string(" is not supported")); + } - if (sb.st_size % dtype_size == 0) - { - files_pointer_size[elem_filename] = std::make_tuple(0, size); - } - else if (sb.st_size == 1) - { - files_pointer_size[elem_filename] = std::make_tuple(0, 0); - } - else - { - throw std::invalid_argument("Wrong size of datatype"); - } + if (strcmp(ext.c_str(), "bit") == 0) + { + ext = "bool"; + } + + int dtype_size = _sizeof_dtype(ext); + std::error_code size_ec; + auto raw_size = trx::fs::file_size(entry_path, size_ec); + if (size_ec) + { + throw std::runtime_error("Failed to stat file: " + elem_filename); + } + + if (raw_size % static_cast(dtype_size) == 0) + { + auto size = raw_size / static_cast(dtype_size); + files_pointer_size[elem_filename] = std::make_tuple(0, static_cast(size)); + } + else if (raw_size == 1) + { + files_pointer_size[elem_filename] = std::make_tuple(0, 0); + } + else + { + throw std::invalid_argument("Wrong size of datatype"); } } - closedir(dir); } std::string get_base(const std::string &delimiter, const std::string &str) { @@ -483,104 +494,84 @@ mio::shared_mmap_sink _create_memmap(std::string filename, std::tuple void copy_dir(const char *src, const char *dst) { - DIR *dir; - struct dirent *entry; - - if (!(dir = opendir(src))) + trx::fs::path src_path(src); + trx::fs::path dst_path(dst); + std::error_code ec; + if (!trx::fs::exists(src_path, ec) || !trx::fs::is_directory(src_path, ec)) + { return; + } - if (mkdir(dst, S_IRWXU) != 0) + if (!trx::fs::create_directories(dst_path, ec) && ec) { - throw std::runtime_error(std::string("Could not create directory ") + dst); + throw std::runtime_error(std::string("Could not create directory ") + dst); } - while ((entry = readdir(dir)) != NULL) + ec.clear(); + for (trx::fs::recursive_directory_iterator it(src_path, ec), end; it != end; it.increment(ec)) { - if (entry->d_type == DT_DIR) + if (ec) { - char path[1024]; - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - char dstpath[1024]; - snprintf(path, sizeof(path), "%s%s%s", src, SEPARATOR.c_str(), entry->d_name); - snprintf(dstpath, sizeof(dstpath), "%s%s%s", dst, SEPARATOR.c_str(), entry->d_name); - copy_dir(path, dstpath); + throw std::runtime_error("Failed to read directory: " + src_path.string()); } - else + const trx::fs::path current = it->path(); + const trx::fs::path rel = current.lexically_relative(src_path); + const trx::fs::path target = dst_path / rel; + std::error_code entry_ec; + if (it->is_directory(entry_ec)) + { + trx::fs::create_directories(target, entry_ec); + if (entry_ec) + { + throw std::runtime_error("Could not create directory " + target.string()); + } + continue; + } + if (!it->is_regular_file(entry_ec)) { - char srcfile[1024]; - char dstfile[1024]; - snprintf(srcfile, sizeof(srcfile), "%s%s%s", src, SEPARATOR.c_str(), entry->d_name); - snprintf(dstfile, sizeof(dstfile), "%s%s%s", dst, SEPARATOR.c_str(), entry->d_name); - copy_file(srcfile, dstfile); + continue; } + copy_file(current.string().c_str(), target.string().c_str()); } - closedir(dir); } - // modified from:https://stackoverflow.com/a/7267734 void copy_file(const char *src, const char *dst) { - int src_fd, dst_fd, n, err; - unsigned char buffer[4096]; - - src_fd = trx_open(src, O_RDONLY); - dst_fd = trx_open(dst, O_CREAT | O_WRONLY, 0666); // maybe keep original permissions? - - while (1) + std::ifstream in(src, std::ios::binary); + if (!in.is_open()) { - err = trx_read(src_fd, buffer, 4096); - if (err == -1) - { - printf("Error reading file.\n"); - exit(1); - } - n = err; - - if (n == 0) - break; - - err = trx_write(dst_fd, buffer, n); - if (err == -1) - { - printf("Error writing to file.\n"); - exit(1); - } + throw std::runtime_error(std::string("Failed to open source file ") + src); + } + std::ofstream out(dst, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + { + throw std::runtime_error(std::string("Failed to open destination file ") + dst); } - trx_close(src_fd); - trx_close(dst_fd); - } - int rm_dir(const char *d) - { - - DIR *dir; - struct dirent *entry; - - if (!(dir = opendir(d))) - return -1; - while ((entry = readdir(dir)) != NULL) + char buffer[4096]; + while (in) { - if (entry->d_type == DT_DIR) - { - char path[1024]; - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - snprintf(path, sizeof(path), "%s%s%s", d, SEPARATOR.c_str(), entry->d_name); - rm_dir(path); - } - else + in.read(buffer, sizeof(buffer)); + std::streamsize n = in.gcount(); + if (n > 0) { - char fn[1024]; - snprintf(fn, sizeof(fn), "%s%s%s", d, SEPARATOR.c_str(), entry->d_name); - if (remove(fn) != 0) + out.write(buffer, n); + if (!out) { - return -1; + throw std::runtime_error(std::string("Error writing to file ") + dst); } } } - closedir(dir); - return rmdir(d); + if (!in.eof()) + { + throw std::runtime_error(std::string("Error reading file ") + src); + } + } + int rm_dir(const char *d) + { + std::error_code ec; + trx::fs::remove_all(d, ec); + return ec ? -1 : 0; } std::string make_temp_dir(const std::string &prefix) @@ -763,50 +754,40 @@ mio::shared_mmap_sink _create_memmap(std::string filename, std::tuple void zip_from_folder(zip_t *zf, const std::string root, const std::string directory, zip_uint32_t compression_standard) { - DIR *dir; - struct dirent *entry; - - if (!(dir = opendir(directory.c_str()))) - return; - - while ((entry = readdir(dir)) != NULL) + std::error_code ec; + for (trx::fs::recursive_directory_iterator it(directory, ec), end; it != end; it.increment(ec)) { - if (entry->d_type == DT_DIR) + if (ec) + { + throw std::runtime_error("Failed to read directory: " + directory); + } + const trx::fs::path current = it->path(); + std::string zip_fname = rm_root(root, current.string()); + std::error_code entry_ec; + if (it->is_directory(entry_ec)) { - char path[1024]; - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - snprintf(path, sizeof(path), "%s%s%s", directory.c_str(), SEPARATOR.c_str(), entry->d_name); - - std::string zip_fname(path); - zip_fname = rm_root(root, zip_fname); zip_dir_add(zf, zip_fname.c_str(), ZIP_FL_ENC_GUESS); - zip_from_folder(zf, root, std::string(path), compression_standard); + continue; } - else + if (!it->is_regular_file(entry_ec)) { - std::string fn; - char fullpath[1024]; - - snprintf(fullpath, sizeof(fullpath), "%s%s%s", directory.c_str(), SEPARATOR.c_str(), entry->d_name); - fn = rm_root(root, std::string(fullpath)); - - zip_source_t *s; + continue; + } - zip_int64_t file_idx = -1; - if ((s = zip_source_file(zf, fullpath, 0, 0)) == NULL || - (file_idx = zip_file_add(zf, fn.c_str(), s, ZIP_FL_ENC_UTF_8)) < 0) - { - zip_source_free(s); - throw std::runtime_error(std::string("Error adding file ") + fn + ": " + zip_strerror(zf)); - } - else if (zip_set_file_compression(zf, file_idx, compression_standard, 0) < 0) - { - throw std::runtime_error(std::string("Error setting compression for ") + fn + ": " + zip_strerror(zf)); - } + const std::string fullpath = current.string(); + zip_source_t *s; + zip_int64_t file_idx = -1; + if ((s = zip_source_file(zf, fullpath.c_str(), 0, 0)) == NULL || + (file_idx = zip_file_add(zf, zip_fname.c_str(), s, ZIP_FL_ENC_UTF_8)) < 0) + { + zip_source_free(s); + throw std::runtime_error(std::string("Error adding file ") + zip_fname + ": " + zip_strerror(zf)); + } + else if (zip_set_file_compression(zf, file_idx, compression_standard, 0) < 0) + { + throw std::runtime_error(std::string("Error setting compression for ") + zip_fname + ": " + zip_strerror(zf)); } } - closedir(dir); } std::string rm_root(const std::string root, const std::string path) diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 40eae48..28c1faa 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -1,7 +1,7 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) project(trx_cpp_test_package LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(trx-cpp CONFIG REQUIRED) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3e4e633..c3175b6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,23 +70,23 @@ endif() add_executable(test_mmap test_trx_mmap.cpp) target_link_libraries(test_mmap PRIVATE trx GTest::gtest_main) -target_compile_features(test_mmap PRIVATE cxx_std_14) +target_compile_features(test_mmap PRIVATE cxx_std_17) add_executable(test_io test_trx_io.cpp) target_link_libraries(test_io PRIVATE trx GTest::gtest_main) -target_compile_features(test_io PRIVATE cxx_std_14) +target_compile_features(test_io PRIVATE cxx_std_17) add_executable(test_streamlines_ops test_trx_streamlines_ops.cpp) target_link_libraries(test_streamlines_ops PRIVATE trx GTest::gtest_main) -target_compile_features(test_streamlines_ops PRIVATE cxx_std_14) +target_compile_features(test_streamlines_ops PRIVATE cxx_std_17) add_executable(test_filesystem test_trx_filesystem.cpp) target_link_libraries(test_filesystem PRIVATE trx GTest::gtest_main) -target_compile_features(test_filesystem PRIVATE cxx_std_14) +target_compile_features(test_filesystem PRIVATE cxx_std_17) add_executable(test_trxfile test_trx_trxfile.cpp) target_link_libraries(test_trxfile PRIVATE trx GTest::gtest_main) -target_compile_features(test_trxfile PRIVATE cxx_std_14) +target_compile_features(test_trxfile PRIVATE cxx_std_17) include(GoogleTest) gtest_discover_tests(test_mmap PROPERTIES diff --git a/tests/test_trx_filesystem.cpp b/tests/test_trx_filesystem.cpp index cef47f2..6dd85de 100644 --- a/tests/test_trx_filesystem.cpp +++ b/tests/test_trx_filesystem.cpp @@ -1,12 +1,12 @@ #include -#include +#include #include #include #include #include -namespace fs = trx::fs; +namespace fs = std::filesystem; namespace { @@ -115,25 +115,25 @@ TEST(TrxFilesystem, PermissionsRoundTrip) write_text_file(file, "perm"); std::error_code ec; - auto perms_before = fs::permissions(file, ec); + auto perms_before = fs::status(file, ec).permissions(); if (ec) { GTEST_SKIP() << "permissions() not supported on this platform"; } - fs::permissions(file, fs::perms::owner_read, ec); + fs::permissions(file, fs::perms::owner_read, fs::perm_options::replace, ec); if (ec) { GTEST_SKIP() << "permissions() failed on this platform"; } - auto perms_after = fs::permissions(file, ec); + auto perms_after = fs::status(file, ec).permissions(); if (!ec) { EXPECT_NE(static_cast(perms_after), 0u); } - fs::permissions(file, perms_before, ec); + fs::permissions(file, perms_before, fs::perm_options::replace, ec); fs::remove_all(root, ec); } @@ -147,7 +147,8 @@ TEST(TrxFilesystem, SymlinkCreateAndDetect) write_text_file(target, "data"); std::error_code ec; - if (!fs::create_symlink(target, link, ec)) + fs::create_symlink(target, link, ec); + if (ec) { GTEST_SKIP() << "Symlink creation not permitted: " << ec.message(); } diff --git a/tests/test_trx_io.cpp b/tests/test_trx_io.cpp index 2bab874..ba93972 100644 --- a/tests/test_trx_io.cpp +++ b/tests/test_trx_io.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include @@ -16,7 +16,7 @@ using namespace Eigen; using namespace trxmmap; -namespace fs = trx::fs; +namespace fs = std::filesystem; namespace { @@ -108,20 +108,16 @@ namespace return normalize_path(path.string()); } - bool is_directory(const fs::path &path) + bool is_dir(const fs::path &path) { std::error_code ec; return fs::is_directory(path, ec) && !ec; } - bool is_regular_file(const fs::path &path) + bool is_regular(const fs::path &path) { - struct stat sb; - if (stat(path.c_str(), &sb) != 0) - { - return false; - } - return S_ISREG(sb.st_mode); + std::error_code ec; + return fs::is_regular_file(path, ec) && !ec; } Matrix load_rasmm_coords(const fs::path &path) @@ -177,7 +173,7 @@ namespace template trxmmap::TrxFile
*load_trx(const fs::path &path) { - if (is_directory(path)) + if (is_dir(path)) { return trxmmap::load_from_directory
(path.string()); } @@ -325,7 +321,7 @@ TEST(TrxFileIo, delete_tmp_gs_dir_rasmm) trxmmap::TrxFile *trx = load_trx(input); std::string tmp_dir = trx->_uncompressed_folder_handle; - if (is_regular_file(input)) + if (is_regular(input)) { ASSERT_FALSE(tmp_dir.empty()); ASSERT_TRUE(fs::exists(tmp_dir)); @@ -335,7 +331,7 @@ TEST(TrxFileIo, delete_tmp_gs_dir_rasmm) expect_allclose(actual, coords); trx->close(); - if (is_regular_file(input)) + if (is_regular(input)) { #if defined(_WIN32) || defined(_WIN64) // Windows can hold file handles briefly after close; avoid flaky removal assertions. @@ -456,49 +452,25 @@ TEST(TrxFileIo, complete_dir_from_trx) { ASSERT_TRUE(fs::exists(input)); trxmmap::TrxFile *trx = load_trx(input); - fs::path dir_to_check = trx->_uncompressed_folder_handle.empty() ? input : trx->_uncompressed_folder_handle; + fs::path dir_to_check = trx->_uncompressed_folder_handle.empty() + ? input + : fs::path(trx->_uncompressed_folder_handle); std::set file_paths; - std::vector pending; - pending.push_back(dir_to_check); - while (!pending.empty()) + std::error_code ec; + for (fs::recursive_directory_iterator it(dir_to_check, ec), end; it != end; it.increment(ec)) { - fs::path current = pending.back(); - pending.pop_back(); - - DIR *dir = opendir(current.c_str()); - if (dir == nullptr) + if (ec) { - continue; + break; } - struct dirent *entry; - while ((entry = readdir(dir)) != nullptr) + if (!it->is_regular_file(ec)) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - { - continue; - } - fs::path child = current / entry->d_name; - if (is_directory(child)) - { - pending.push_back(child); - } - else if (is_regular_file(child)) - { - std::string full = normalize_path(child); - std::string base = normalize_path(dir_to_check); - if (full.rfind(base, 0) == 0) - { - std::string rel = full.substr(base.size()); - if (!rel.empty() && rel[0] == '/') - { - rel.erase(0, 1); - } - file_paths.insert(rel); - } - } + ec.clear(); + continue; } - closedir(dir); + fs::path rel = it->path().lexically_relative(dir_to_check); + file_paths.insert(normalize_path(rel.string())); } EXPECT_EQ(file_paths, expected_content); diff --git a/tests/test_trx_mmap.cpp b/tests/test_trx_mmap.cpp index b7364c0..1bb77f3 100644 --- a/tests/test_trx_mmap.cpp +++ b/tests/test_trx_mmap.cpp @@ -3,13 +3,13 @@ #include #include #include -#include +#include #include #include using namespace Eigen; using namespace trxmmap; -namespace fs = trx::fs; +namespace fs = std::filesystem; namespace { diff --git a/tests/test_trx_trxfile.cpp b/tests/test_trx_trxfile.cpp index 71be39d..60397f8 100644 --- a/tests/test_trx_trxfile.cpp +++ b/tests/test_trx_trxfile.cpp @@ -6,7 +6,7 @@ #undef private #include -#include +#include #include #include @@ -14,7 +14,7 @@ using namespace Eigen; using namespace trxmmap; -namespace fs = trx::fs; +namespace fs = std::filesystem; namespace { From 441d83456cf0273943ba77d29df4aa3002b9134c Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 11:17:17 -0500 Subject: [PATCH 2/8] windows compat --- src/trx.cpp | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/trx.cpp b/src/trx.cpp index d6d975b..5edeb96 100644 --- a/src/trx.cpp +++ b/src/trx.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #define SYSERROR() errno @@ -626,31 +627,41 @@ mio::shared_mmap_sink _create_memmap(std::string filename, std::tuple } if (base_dir.empty()) { +#if defined(_WIN32) || defined(_WIN64) + base_dir = "."; +#else base_dir = "/tmp"; +#endif } - trx::fs::path tmpl = trx::fs::path(base_dir) / (prefix + "_XXXXXX"); - std::string tmpl_str = tmpl.string(); - std::vector buf(tmpl_str.begin(), tmpl_str.end()); - buf.push_back('\0'); -#if defined(_WIN32) || defined(_WIN64) - if (_mktemp_s(buf.data(), buf.size()) != 0) + trx::fs::path base_path(base_dir); + std::error_code ec; + if (!trx::fs::exists(base_path, ec)) { - throw std::runtime_error("Failed to create temporary directory"); - } - if (_mkdir(buf.data()) != 0) - { - throw std::runtime_error("Failed to create temporary directory"); + ec.clear(); + trx::fs::create_directories(base_path, ec); + if (ec) + { + throw std::runtime_error("Failed to create base temp directory: " + base_dir); + } } - return std::string(buf.data()); -#else - char *dirname = mkdtemp(buf.data()); - if (dirname == nullptr) + + static std::mt19937_64 rng(std::random_device{}()); + std::uniform_int_distribution dist; + for (int attempt = 0; attempt < 100; ++attempt) { - throw std::runtime_error("Failed to create temporary directory"); + trx::fs::path candidate = base_path / (prefix + "_" + std::to_string(dist(rng))); + ec.clear(); + if (trx::fs::create_directory(candidate, ec)) + { + return candidate.string(); + } + if (ec && ec != std::errc::file_exists) + { + throw std::runtime_error("Failed to create temporary directory: " + ec.message()); + } } - return std::string(dirname); -#endif + throw std::runtime_error("Failed to create temporary directory"); } std::string extract_zip_to_directory(zip_t *zfolder) From e420020936d7d0de60494e60229ec838a82a964f Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 11:22:38 -0500 Subject: [PATCH 3/8] more windows --- include/trx/trx.tpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/trx/trx.tpp b/include/trx/trx.tpp index ef1c6d2..f507c4e 100644 --- a/include/trx/trx.tpp +++ b/include/trx/trx.tpp @@ -574,7 +574,7 @@ TrxFile
*TrxFile
::_create_trx_from_pointer(json header, std::map(size)); } std::string data_name = path_basename(base); @@ -606,7 +606,7 @@ TrxFile
*TrxFile
::_create_trx_from_pointer(json header, std::map(size), 1); } trx->groups[base] = new MMappedMatrix(); trx->groups[base]->mmap = trxmmap::_create_memmap(filename, shape, "r+", ext, mem_adress); From 132ab9f0200fde4f0e909c2c7e1258c6fe686457 Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 11:30:27 -0500 Subject: [PATCH 4/8] windows again --- CMakeLists.txt | 1 + include/trx/trx.tpp | 14 ++++++-------- tests/test_trx_filesystem.cpp | 2 +- tests/test_trx_io.cpp | 9 +++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e38573..37ac913 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ option(TRX_BUILD_EXAMPLES "Build trx example commandline programs" ON) if(MSVC) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + add_compile_definitions(NOMINMAX) endif() find_package(libzip REQUIRED) diff --git a/include/trx/trx.tpp b/include/trx/trx.tpp index f507c4e..ba0ca8a 100644 --- a/include/trx/trx.tpp +++ b/include/trx/trx.tpp @@ -898,7 +898,7 @@ void TrxFile
::resize(int nb_streamlines, int nb_vertices, bool delete_dpg) { if (!this->_copy_safe) { - std::invalid_argument("Cannot resize a sliced dataset."); + throw std::invalid_argument("Cannot resize a sliced dataset."); } std::tuple sp_end = this->_get_real_len(); @@ -1230,15 +1230,12 @@ void save(TrxFile
&trx, const std::string filename, zip_uint32_t compression } else { - struct stat sb; - - struct stat tmp_sb; - if (stat(tmp_dir_name.c_str(), &tmp_sb) != 0 || !S_ISDIR(tmp_sb.st_mode)) + std::error_code ec; + if (!trx::fs::exists(tmp_dir_name, ec) || !trx::fs::is_directory(tmp_dir_name, ec)) { throw std::runtime_error("Temporary TRX directory does not exist: " + tmp_dir_name); } - - if (stat(filename.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) + if (trx::fs::exists(filename, ec) && trx::fs::is_directory(filename, ec)) { if (rm_dir(filename.c_str()) != 0) { @@ -1257,7 +1254,8 @@ void save(TrxFile
&trx, const std::string filename, zip_uint32_t compression } } copy_dir(tmp_dir_name.c_str(), filename.c_str()); - if (stat(filename.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) + ec.clear(); + if (!trx::fs::exists(filename, ec) || !trx::fs::is_directory(filename, ec)) { throw std::runtime_error("Failed to create output directory: " + filename); } diff --git a/tests/test_trx_filesystem.cpp b/tests/test_trx_filesystem.cpp index 6dd85de..add5c26 100644 --- a/tests/test_trx_filesystem.cpp +++ b/tests/test_trx_filesystem.cpp @@ -99,7 +99,7 @@ TEST(TrxFilesystem, FileSizeAndRemoveAll) EXPECT_EQ(fs::file_size(file), 3u); - EXPECT_THROW(fs::file_size(root), std::runtime_error); + EXPECT_THROW((void)fs::file_size(root), std::runtime_error); std::error_code ec; fs::remove_all(root, ec); diff --git a/tests/test_trx_io.cpp b/tests/test_trx_io.cpp index ba93972..5f7746b 100644 --- a/tests/test_trx_io.cpp +++ b/tests/test_trx_io.cpp @@ -232,12 +232,13 @@ namespace std::string get_current_working_dir() { - char buffer[PATH_MAX]; - if (getcwd(buffer, sizeof(buffer)) == nullptr) + std::error_code ec; + auto cwd = fs::current_path(ec); + if (ec) { - throw std::runtime_error("Failed to get current working directory"); + throw std::runtime_error("Failed to get current working directory: " + ec.message()); } - return std::string(buffer); + return cwd.string(); } bool wait_for_path_gone(const fs::path &path, int retries = 10, int delay_ms = 50) From 291059e7f4fd630b9eea9bb85878c86d103ee24f Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 11:38:35 -0500 Subject: [PATCH 5/8] windows path handling --- include/trx/trx.tpp | 44 ++++++++++++++++++++++++-------------------- src/trx.cpp | 6 ++---- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/include/trx/trx.tpp b/include/trx/trx.tpp index ba0ca8a..fcedff9 100644 --- a/include/trx/trx.tpp +++ b/include/trx/trx.tpp @@ -401,7 +401,30 @@ TrxFile
*TrxFile
::_create_trx_from_pointer(json header, std::map base_tuple = _split_ext_with_dimensionality(elem_filename); @@ -417,25 +440,6 @@ TrxFile
*TrxFile
::_create_trx_from_pointer(json header, std::map(x->second); long long size = std::get<1>(x->second); - std::string stripped = root; - - // TODO : will not work on windows - if (stripped.rfind("/") == stripped.size() - 1) - { - stripped = stripped.substr(0, stripped.size() - 1); - } - - if (root.compare("") != 0 && folder.rfind(stripped, stripped.size()) == 0) - { - // 1 for the first forward slash - folder = folder.replace(0, root.size(), ""); - - if (folder[0] == SEPARATOR.c_str()[0]) - { - folder = folder.substr(1, folder.size()); - } - } - if (base.compare("positions") == 0 && (folder.compare("") == 0 || folder.compare(".") == 0)) { if (size != static_cast(trx->header["NB_VERTICES"].int_value()) * 3 || dim != 3) diff --git a/src/trx.cpp b/src/trx.cpp index 5edeb96..17d5da1 100644 --- a/src/trx.cpp +++ b/src/trx.cpp @@ -309,9 +309,7 @@ bool _is_path_within(const trx::fs::path &child, const trx::fs::path &parent) } std::tuple _split_ext_with_dimensionality(const std::string filename) { - - // TODO: won't work on windows and not validating OS type - std::string base = get_base("/", filename); + std::string base = path_basename(filename); size_t num_splits = std::count(base.begin(), base.end(), '.'); int dim; @@ -321,7 +319,7 @@ bool _is_path_within(const trx::fs::path &child, const trx::fs::path &parent) throw std::invalid_argument("Invalid filename"); } - std::string ext = get_ext(filename); + std::string ext = get_ext(base); base = base.substr(0, base.length() - ext.length() - 1); From 0c1abf9d0a2b96e5f737411361b8e5f1aa580f91 Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 11:45:06 -0500 Subject: [PATCH 6/8] match std::filesystem behavior in test --- tests/test_trx_filesystem.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_trx_filesystem.cpp b/tests/test_trx_filesystem.cpp index add5c26..75e6821 100644 --- a/tests/test_trx_filesystem.cpp +++ b/tests/test_trx_filesystem.cpp @@ -99,7 +99,11 @@ TEST(TrxFilesystem, FileSizeAndRemoveAll) EXPECT_EQ(fs::file_size(file), 3u); - EXPECT_THROW((void)fs::file_size(root), std::runtime_error); + std::error_code size_ec; + auto size = fs::file_size(root, size_ec); + EXPECT_TRUE(size_ec); + EXPECT_EQ(size, static_cast(-1)); + EXPECT_TRUE(size_ec == std::errc::is_a_directory || size_ec == std::errc::invalid_argument); std::error_code ec; fs::remove_all(root, ec); From 97d9b440e4ef8f0cb8f9b45d0eb0c561361fbff8 Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 12:04:51 -0500 Subject: [PATCH 7/8] more windows --- tests/test_trx_filesystem.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_trx_filesystem.cpp b/tests/test_trx_filesystem.cpp index 75e6821..16eca90 100644 --- a/tests/test_trx_filesystem.cpp +++ b/tests/test_trx_filesystem.cpp @@ -101,9 +101,15 @@ TEST(TrxFilesystem, FileSizeAndRemoveAll) std::error_code size_ec; auto size = fs::file_size(root, size_ec); - EXPECT_TRUE(size_ec); - EXPECT_EQ(size, static_cast(-1)); - EXPECT_TRUE(size_ec == std::errc::is_a_directory || size_ec == std::errc::invalid_argument); + if (size_ec) + { + EXPECT_TRUE(size_ec == std::errc::is_a_directory || size_ec == std::errc::invalid_argument); + } + else + { + // Some platforms report size 0 for directories without error. + EXPECT_EQ(size, 0u); + } std::error_code ec; fs::remove_all(root, ec); From 301c5db4aa0c16e70edd654a969d204eee557e9d Mon Sep 17 00:00:00 2001 From: mattcieslak Date: Wed, 28 Jan 2026 12:12:55 -0500 Subject: [PATCH 8/8] re-enable codecov --- .github/workflows/trx-cpp-tests.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/trx-cpp-tests.yml b/.github/workflows/trx-cpp-tests.yml index 2f61216..7236c04 100644 --- a/.github/workflows/trx-cpp-tests.yml +++ b/.github/workflows/trx-cpp-tests.yml @@ -67,13 +67,13 @@ jobs: --output-file coverage.info lcov --summary coverage.info - # - name: Upload to Codecov - # uses: codecov/codecov-action@v4 - # with: - # files: coverage.info - # flags: linux - # fail_ci_if_error: true - # token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + files: coverage.info + flags: linux + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} - name: Upload coverage report uses: actions/upload-artifact@v4