From f9621ac38d97335d01da214e3d9e26d85066d044 Mon Sep 17 00:00:00 2001 From: Travis Gockel Date: Thu, 30 Jan 2020 12:06:54 -0700 Subject: [PATCH] Removes all usage of Boost from the project. Boost has been a build-time dependency for a while, but packaging in C++ is such a pain that it seems easier to reinvent the wheel instead of continuing to deal with the constantly-changing packaging of Boost across 5 distros. Fixes issue #137. --- .travis.yml | 10 ++- CMakeLists.txt | 18 ---- config/docker/arch/Dockerfile | 1 - .../{ubuntu-18.10 => debian-10.1}/Dockerfile | 3 +- .../{ubuntu-19.04 => debian-10.2}/Dockerfile | 3 +- config/docker/fedora-27/Dockerfile | 1 - config/docker/fedora-30/Dockerfile | 1 - config/docker/ubuntu-16.04/Dockerfile | 1 - config/docker/ubuntu-18.04/Dockerfile | 1 - config/docker/ubuntu-19.10/Dockerfile | 12 +++ src/json-benchmark/main.cpp | 66 ++++++++------- src/jsonv-tests/benchmark_tests.cpp | 15 +++- src/jsonv-tests/filesystem_util.cpp | 84 +++++++++++++++---- src/jsonv/path.cpp | 26 ++++-- 14 files changed, 157 insertions(+), 85 deletions(-) rename config/docker/{ubuntu-18.10 => debian-10.1}/Dockerfile (85%) rename config/docker/{ubuntu-19.04 => debian-10.2}/Dockerfile (85%) create mode 100644 config/docker/ubuntu-19.10/Dockerfile diff --git a/.travis.yml b/.travis.yml index d4ef748..2ea4aca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,14 @@ env: - DISTRO=ubuntu-16.04 CONF=Release - DISTRO=ubuntu-18.04 CONF=Debug - DISTRO=ubuntu-18.04 CONF=Release - - DISTRO=ubuntu-18.10 CONF=Debug - - DISTRO=ubuntu-18.10 CONF=Release - - DISTRO=ubuntu-19.04 CONF=Debug - - DISTRO=ubuntu-19.04 CONF=Release + - DISTRO=ubuntu-19.10 CONF=Debug + - DISTRO=ubuntu-19.10 CONF=Release - DISTRO=debian-9.9 CONF=Debug - DISTRO=debian-9.9 CONF=Release + - DISTRO=debian-10.1 CONF=Debug + - DISTRO=debian-10.1 CONF=Release + - DISTRO=debian-10.2 CONF=Debug + - DISTRO=debian-10.2 CONF=Release - DISTRO=opensuse-15.1 CONF=Debug - DISTRO=opensuse-15.1 CONF=Release - DISTRO=fedora-27 CONF=Debug diff --git a/CMakeLists.txt b/CMakeLists.txt index e4fe291..2d29722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,12 +53,6 @@ option(JSONV_BUILD_TESTS OFF ) -set(REQUIRED_BOOST_LIBRARIES) - -if (JSONV_BUILD_TESTS) - list(APPEND REQUIRED_BOOST_LIBRARIES "filesystem" "system") -endif() - ############################# # String View Configuration # ############################# @@ -147,13 +141,6 @@ include_directories("${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}/src" ) -set(Boost_USE_MULTITHREADED ON) -find_package(Boost - COMPONENTS "${REQUIRED_BOOST_LIBRARIES}" - REQUIRED - ) -include_directories(${Boost_INCLUDE_DIRS}) - add_definitions("-DJSONV_TEST_DATA_DIR=\"${CMAKE_SOURCE_DIR}/src/jsonv-tests/data\"") configure_file(libjsonv.pc.in libjsonv.pc) @@ -169,16 +156,12 @@ set_target_properties(jsonv SOVERSION ${JSONV_SO_VERSION} VERSION ${JSONV_SO_VERSION} ) -if (Boost_LIBRARIES) - target_link_libraries(jsonv ${Boost_LIBRARIES}) -endif() if (JSONV_BUILD_TESTS) file(GLOB_RECURSE jsonv_tests_cpps RELATIVE_PATH "." "src/jsonv-tests/*.cpp") add_executable(jsonv-tests ${jsonv_tests_cpps}) target_link_libraries(jsonv-tests "jsonv" - ${Boost_LIBRARIES} ) add_custom_target(check @@ -247,7 +230,6 @@ if (BENCHMARK) add_executable(json-benchmark ${BENCHMARK_CPPS}) target_link_libraries(json-benchmark ${BENCHMARK_LIBS} - ${Boost_LIBRARIES} ) endif(BENCHMARK) diff --git a/config/docker/arch/Dockerfile b/config/docker/arch/Dockerfile index 81fbe10..cac447d 100644 --- a/config/docker/arch/Dockerfile +++ b/config/docker/arch/Dockerfile @@ -2,7 +2,6 @@ FROM archlinux/base LABEL maintainer="Travis Gockel " RUN pacman -Sy --noconfirm \ - boost \ cmake \ gcc \ git \ diff --git a/config/docker/ubuntu-18.10/Dockerfile b/config/docker/debian-10.1/Dockerfile similarity index 85% rename from config/docker/ubuntu-18.10/Dockerfile rename to config/docker/debian-10.1/Dockerfile index 1905375..b9abebc 100644 --- a/config/docker/ubuntu-18.10/Dockerfile +++ b/config/docker/debian-10.1/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:18.10 +FROM debian:10.1 LABEL maintainer="Travis Gockel " RUN apt-get update \ @@ -7,7 +7,6 @@ RUN apt-get update \ grep \ g++ \ lcov \ - libboost-all-dev \ ninja-build CMD ["/root/jsonv/config/run-tests"] diff --git a/config/docker/ubuntu-19.04/Dockerfile b/config/docker/debian-10.2/Dockerfile similarity index 85% rename from config/docker/ubuntu-19.04/Dockerfile rename to config/docker/debian-10.2/Dockerfile index 78e664f..6a7c236 100644 --- a/config/docker/ubuntu-19.04/Dockerfile +++ b/config/docker/debian-10.2/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:19.04 +FROM debian:10.2 LABEL maintainer="Travis Gockel " RUN apt-get update \ @@ -7,7 +7,6 @@ RUN apt-get update \ grep \ g++ \ lcov \ - libboost-all-dev \ ninja-build CMD ["/root/jsonv/config/run-tests"] diff --git a/config/docker/fedora-27/Dockerfile b/config/docker/fedora-27/Dockerfile index d8f29dd..f518928 100644 --- a/config/docker/fedora-27/Dockerfile +++ b/config/docker/fedora-27/Dockerfile @@ -3,7 +3,6 @@ LABEL maintainer="Travis Gockel " RUN dnf refresh \ && dnf install -y \ - boost-devel \ cmake \ grep \ gcc-c++ \ diff --git a/config/docker/fedora-30/Dockerfile b/config/docker/fedora-30/Dockerfile index 92f9632..6377d45 100644 --- a/config/docker/fedora-30/Dockerfile +++ b/config/docker/fedora-30/Dockerfile @@ -2,7 +2,6 @@ FROM fedora:30 LABEL maintainer="Travis Gockel " RUN dnf install -y --refresh \ - boost-devel \ cmake \ grep \ gcc-c++ \ diff --git a/config/docker/ubuntu-16.04/Dockerfile b/config/docker/ubuntu-16.04/Dockerfile index 8e416bd..e7e4083 100644 --- a/config/docker/ubuntu-16.04/Dockerfile +++ b/config/docker/ubuntu-16.04/Dockerfile @@ -7,7 +7,6 @@ RUN apt-get update \ grep \ g++ \ lcov \ - libboost-all-dev \ ninja-build CMD ["/root/jsonv/config/run-tests"] diff --git a/config/docker/ubuntu-18.04/Dockerfile b/config/docker/ubuntu-18.04/Dockerfile index 1f8cdd3..cafd8c5 100644 --- a/config/docker/ubuntu-18.04/Dockerfile +++ b/config/docker/ubuntu-18.04/Dockerfile @@ -7,7 +7,6 @@ RUN apt-get update \ grep \ g++ \ lcov \ - libboost-all-dev \ ninja-build CMD ["/root/jsonv/config/run-tests"] diff --git a/config/docker/ubuntu-19.10/Dockerfile b/config/docker/ubuntu-19.10/Dockerfile new file mode 100644 index 0000000..191b647 --- /dev/null +++ b/config/docker/ubuntu-19.10/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:19.10 +LABEL maintainer="Travis Gockel " + +RUN apt-get update \ + && apt-get install --yes \ + cmake \ + grep \ + g++ \ + lcov \ + ninja-build + +CMD ["/root/jsonv/config/run-tests"] diff --git a/src/json-benchmark/main.cpp b/src/json-benchmark/main.cpp index 720e84b..29707b5 100644 --- a/src/json-benchmark/main.cpp +++ b/src/json-benchmark/main.cpp @@ -1,5 +1,5 @@ /** \file - * + * * Copyright (c) 2015 by Travis Gockel. All rights reserved. * * This program is free software: you can redistribute it and/or modify it under the terms of the Apache License @@ -20,56 +20,66 @@ #include #include -#include - using namespace jsonv; +static int extract_int(string_view src) +{ + auto src_end = src.data() + src.size(); + char* scan_end = nullptr; + auto val = std::strtol(src.data(), &scan_end, 10); + + if (scan_end == src_end) + return int(val); + else + throw std::invalid_argument(std::string("Could not extract integer from \"") + std::string(src) + "\""); +} + struct generated_json_settings { std::uniform_int_distribution kind_distribution{0, 6}; - + template jsonv::kind kind(TRng& rng, std::size_t current_depth) { if (current_depth < 2) return jsonv::kind::object; - + auto k = static_cast(kind_distribution(rng)); if (current_depth > 5 && (k == jsonv::kind::array || k == jsonv::kind::object)) return kind(rng, current_depth); else return k; } - + std::uniform_int_distribution array_length_distribution{0, 100}; template std::size_t array_length(TRng& rng) { return array_length_distribution(rng); } - + std::uniform_int_distribution object_size_distribution{5, 25}; template std::size_t object_size(TRng& rng) { return object_size_distribution(rng); } - + std::uniform_int_distribution string_length_distribution{0, 100}; template std::size_t string_length(TRng& rng) { return string_length_distribution(rng); } - - + + std::normal_distribution<> decimal_distribution{1.0, 40.0}; template double decimal(TRng& rng) { return decimal_distribution(rng); } - + std::uniform_int_distribution integer_distribution{~0}; template std::int64_t integer(TRng& rng) @@ -125,48 +135,48 @@ class stopwatch using clock = std::chrono::steady_clock; using time_point = clock::time_point; using duration = std::chrono::nanoseconds; - + struct ticker { stopwatch* owner; time_point start_time; - + ticker(stopwatch* o) : owner(o), start_time(clock::now()) { } - + ticker(ticker&& src) : owner(src.owner), start_time(src.start_time) { src.owner = nullptr; } - + ~ticker() { if (owner) owner->add_time(clock::now() - start_time); } }; - + public: stopwatch() : tick_count(0), total_time(duration(0)) { } - + ticker start() { return ticker(this); } - + void add_time(duration dur) { ++tick_count; total_time += dur; } - + public: std::size_t tick_count; duration total_time; @@ -181,7 +191,7 @@ static std::string get_encoded_json() buff << in.rdbuf(); return buff.str(); } - + std::ostringstream encoded_stream; ostream_pretty_encoder out(encoded_stream); std::mt19937_64 rng{std::random_device()()}; @@ -189,7 +199,7 @@ static std::string get_encoded_json() value val = generate_json(rng, settings); out.encode(val); std::string encoded = encoded_stream.str(); - + // save the string to a file in case we want to re-use it std::ofstream file("temp.json", std::ofstream::out | std::ofstream::trunc); file << encoded; @@ -199,22 +209,22 @@ static std::string get_encoded_json() int main(int argc, char** argv) { using namespace json_benchmark; - + std::string filter; if (argc >= 2) filter = argv[1]; - + int loop_count = 10; if (argc >= 3) - loop_count = boost::lexical_cast(argv[2]); - + loop_count = extract_int(argv[2]); + std::string encoded = get_encoded_json(); - + for (const benchmark_suite* suite : benchmark_suite::all()) { if (!filter.empty() && filter != suite->name()) continue; - + std::cout << std::endl; stopwatch watch; for (int idx = 1; idx <= loop_count; ++idx) @@ -225,7 +235,7 @@ int main(int argc, char** argv) suite->parse_test(encoded); } std::cout << std::endl; - + auto average = std::chrono::duration_cast>(watch.total_time) / watch.tick_count; std::cout << suite->name() << '\t' << average.count();// << std::endl; } diff --git a/src/jsonv-tests/benchmark_tests.cpp b/src/jsonv-tests/benchmark_tests.cpp index 97bf99f..43413f1 100644 --- a/src/jsonv-tests/benchmark_tests.cpp +++ b/src/jsonv-tests/benchmark_tests.cpp @@ -1,6 +1,6 @@ /** \file - * - * Copyright (c) 2016 by Travis Gockel. All rights reserved. + * + * Copyright (c) 2016-2019 by Travis Gockel. All rights reserved. * * This program is free software: you can redistribute it and/or modify it under the terms of the Apache License * as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later @@ -73,8 +73,15 @@ class benchmark_test_initializer { recursive_directory_for_each(rootpath, ".json", [&, this] (const std::string& path) { - _tests.emplace_back(new benchmark_test([] (const std::string& p) { return p; }, "ifstream", path)); - _tests.emplace_back(new benchmark_test(load_from_file, "string", path)); + if (path.find("fail") == std::string::npos) + { + _tests.emplace_back(new benchmark_test([] (const std::string& p) { return p; }, + "ifstream", + path + ) + ); + _tests.emplace_back(new benchmark_test(load_from_file, "string", path)); + } }); } diff --git a/src/jsonv-tests/filesystem_util.cpp b/src/jsonv-tests/filesystem_util.cpp index e29619b..c60edf6 100644 --- a/src/jsonv-tests/filesystem_util.cpp +++ b/src/jsonv-tests/filesystem_util.cpp @@ -1,6 +1,6 @@ /** \file * Utility functions for interacting with the filesystem. - * + * * Copyright (c) 2015 by Travis Gockel. All rights reserved. * * This program is free software: you can redistribute it and/or modify it under the terms of the Apache License @@ -27,7 +27,40 @@ std::string test_path(const std::string& path) } -#ifdef _MSC_VER +// TODO: libstdc++ requires explicit linking to libstdc++fs, but CMake does not have great cross-platform support for +// linking when it is needed. Since a POSIX fallback exists anyway, we will always just use it on non-Windows platforms. +// The `0` should be taken out when `` support actually becomes universal. +#if 0 && __has_include() + +#include + +namespace jsonv_test +{ + +namespace fs = std::filesystem; + +std::string filename(std::string path) +{ + return fs::path(path).filename().string(); +} + +void recursive_directory_for_each(const std::string& root_path_name, + const std::string& extension_filter, + const std::function action + ) +{ + fs::path rootpath(root_path_name); + for (fs::directory_iterator iter(rootpath); iter != fs::directory_iterator(); ++iter) + { + fs::path p = *iter; + if (extension_filter.empty() || p.extension() == extension_filter) + action(p.string()); + } +} + +} + +#elif defined _MSC_VER #include @@ -49,7 +82,7 @@ void recursive_directory_for_each(const std::string& HANDLE search_handle = NULL; std::string filter = root_path_name + "\\*" + extension_filter; - + if ((search_handle = FindFirstFileA(filter.c_str(), &found_file)) == INVALID_HANDLE_VALUE) return; @@ -78,18 +111,21 @@ void recursive_directory_for_each(const std::string& } -#else +#else /* No and not Windows, so assume POSIX API...remove when is everywhere */ + +#include -#include +#include +#include +#include namespace jsonv_test { -namespace fs = boost::filesystem; - std::string filename(std::string path) { - return fs::path(path).filename().string(); + auto pos = path.find_last_of('/'); + return path.substr(pos + 1); } void recursive_directory_for_each(const std::string& root_path_name, @@ -97,15 +133,35 @@ void recursive_directory_for_each(const std::string& const std::function action ) { - fs::path rootpath(root_path_name); - for (fs::directory_iterator iter(rootpath); iter != fs::directory_iterator(); ++iter) + if (auto dirp = ::opendir(root_path_name.c_str())) { - fs::path p = *iter; - if (extension_filter.empty() || p.extension() == extension_filter) - action(p.string()); + auto close_dirp = jsonv::detail::on_scope_exit([&] { ::closedir(dirp); }); + + while (auto entp = ::readdir(dirp)) + { + // skip "." and ".." + if (std::strcmp(entp->d_name, ".") == 0 + || std::strcmp(entp->d_name, "..") == 0 + ) + { + continue; + } + + std::string sub = root_path_name + "/" + entp->d_name; + + if ((entp->d_type & DT_DIR) == DT_DIR) + { + recursive_directory_for_each(sub, extension_filter, action); + } + else + { + if (extension_filter.empty() || sub.rfind(extension_filter) != std::string::npos) + action(sub); + } + } } } } -#endif \ No newline at end of file +#endif diff --git a/src/jsonv/path.cpp b/src/jsonv/path.cpp index d03f910..8617418 100644 --- a/src/jsonv/path.cpp +++ b/src/jsonv/path.cpp @@ -1,5 +1,5 @@ /** \file - * + * * Copyright (c) 2014 by Travis Gockel. All rights reserved. * * This program is free software: you can redistribute it and/or modify it under the terms of the Apache License @@ -21,8 +21,6 @@ #include #include -#include - namespace jsonv { @@ -120,7 +118,7 @@ path_element& path_element::operator=(const path_element& src) _data.key = src._data.key; } } - + return *this; } @@ -162,7 +160,7 @@ path_element& path_element::operator=(path_element&& src) noexcept _data.key = std::move(src._data.key); } } - + return *this; } @@ -269,6 +267,18 @@ path& path::operator=(path&& src) noexcept } path::~path() noexcept = default; +static std::size_t extract_size_t(string_view src) +{ + auto src_end = src.data() + src.size(); + char* scan_end = nullptr; + auto val = std::strtoull(src.data(), &scan_end, 10); + + if (scan_end == src_end) + return val; + else + throw std::invalid_argument(std::string("Could not extract integer from \"") + std::string(src) + "\""); +} + path path::create(string_view specification) { path out; @@ -285,17 +295,17 @@ path path::create(string_view specification) if (match.at(1) == '\"') out += detail::get_string_decoder(parse_options::encoding::utf8)(match.substr(2, match.size() - 4)); else - out += boost::lexical_cast(match.data() + 1, match.size() - 2); + out += extract_size_t(string_view(match.data() + 1, match.size() - 2)); break; default: throw std::invalid_argument(std::string("Invalid specification \"") + std::string(specification) + "\". " +"Syntax error at \"" + std::string(remaining) + "\"" ); } - + remaining.remove_prefix(match.size()); } - + return out; }