diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5674718..9639b0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,57 +10,16 @@ jobs: build: if: ${{ github.repository == 'Netflix/spectator-cpp' }} runs-on: ubuntu-latest - env: - BUILD_DIR: "cmake-build" - BUILD_TYPE: "Debug" - CC: "gcc-11" - CXX: "g++-11" - LANG: "en_US.UTF-8" steps: - uses: actions/checkout@v4 - - name: Conan+Cmake Cache - uses: actions/cache@v4 - with: - path: | - ~/.conan - ~/work/spectator-cpp/spectator-cpp/cmake-build - key: ${{ runner.os }}-conan-cmake - - name: Install System Dependencies run: | sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update && sudo apt-get install -y binutils-dev g++-11 libiberty-dev - - echo "==== cmake ====" - cmake -version - - echo "==== python ====" - python3 -V - - echo "==== conan ====" - pip install -r requirements.txt - conan --version - if [[ ! -f ~/.conan/profiles/default ]]; then conan profile new default --detect; fi - conan profile update settings.compiler.libcxx=libstdc++11 default - - - name: Install Project Dependencies - run: | - if [[ "$BUILD_TYPE" == "Debug" ]]; then - conan install . --build --install-folder $BUILD_DIR --profile ./sanitized - else - conan install . --build=missing --install-folder $BUILD_DIR - fi - - - name: Build spectator-cpp - run: | - cd $BUILD_DIR - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. - cmake --build . - echo "==== ldd ====" - ldd bin/spectatord_main || true + sudo apt-get update && sudo apt-get install -y binutils-dev g++-13 libiberty-dev - - name: Test spectator-cpp + - name: Build run: | - cd $BUILD_DIR - GTEST_COLOR=1 ctest --verbose + ./setup-venv.sh + source venv/bin/activate + ./build.sh diff --git a/.gitignore b/.gitignore index 8d9970f..faa4c63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .idea/ -cmake-build-debug +CMakeUserPresets.json cmake-build/ +conan_provider.cmake spectator/valid_chars.inc venv/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f2c869..6818310 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,19 @@ project(spectator-cpp) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -add_compile_options(-fno-omit-frame-pointer "$<$:-fsanitize=address>") -add_link_options(-fno-omit-frame-pointer "$<$:-fsanitize=address>") +add_compile_options(-pedantic -Werror -Wall -Wno-missing-braces -fno-omit-frame-pointer "$<$:-fsanitize=address>") + +find_package(absl REQUIRED) +find_package(asio REQUIRED) +find_package(Backward REQUIRED) +find_package(fmt REQUIRED) +find_package(GTest REQUIRED) +find_package(spdlog REQUIRED) include(CTest) -include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) -conan_basic_setup() #-- spectator_test test executable file(GLOB spectator_test_source_files @@ -19,7 +25,10 @@ file(GLOB spectator_test_source_files "spectator/test_*.h" ) add_executable(spectator_test ${spectator_test_source_files}) -target_link_libraries(spectator_test spectator ${CONAN_LIBS}) +target_link_libraries(spectator_test + spectator + gtest::gtest +) add_test( NAME spectator_test COMMAND spectator_test @@ -41,8 +50,13 @@ add_library(spectator "spectator/stateless_meters.h" "spectator/valid_chars.inc" ) -target_link_libraries(spectator ${CONAN_LIBS}) -target_link_options(spectator PRIVATE "$<$:-static-libstdc++>") +target_link_libraries(spectator + abseil::abseil + asio::asio + Backward::Backward + fmt::fmt + spdlog::spdlog +) #-- generator tools add_executable(gen_valid_chars "tools/gen_valid_chars.cc") diff --git a/README.md b/README.md index fde5020..748636c 100644 --- a/README.md +++ b/README.md @@ -86,18 +86,24 @@ fills up, the `Publisher` will not send nay meters to the sidecar. Therefore, if meters at a high rate, you should either keep the buffer very small, or do not configure a buffer size at all, which will fall back to the "publish immediately" mode of operation. -## Local Development +## Local & IDE Configuration ```shell # setup python venv and activate, to gain access to conan cli ./setup-venv.sh source venv/bin/activate -# link clion default build directory to our build directory -ln -s cmake-build cmake-build-debug - -./build.sh # [clean|clean --force|skiptest] +./build.sh # [clean|clean --confirm|skiptest] ``` -* CLion > Preferences > Plugins > Marketplace > Conan > Install -* CLion > Preferences > Build, Execution, Deploy > Conan > Conan Executable: $PROJECT_HOME/venv/bin/conan +* Install the Conan plugin for CLion. + * CLion > Settings > Plugins > Marketplace > Conan > Install +* Configure the Conan plugin. + * The easiest way to configure CLion to work with Conan is to build the project first from the command line. + * This will establish the `$PROJECT_HOME/CMakeUserPresets.json` file, which will allow you to choose the custom + CMake configuration created by Conan when creating a new CMake project. Using this custom profile will ensure + that sources are properly indexed and explorable. + * Open the project. The wizard will show three CMake profiles. + * Disable the default Cmake `Debug` profile. + * Enable the CMake `conan-debug` profile. + * CLion > View > Tool Windows > Conan > (gear) > Conan Executable: `$PROJECT_HOME/venv/bin/conan` diff --git a/build.sh b/build.sh index aed55f7..fcedf35 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,11 @@ #!/usr/bin/env bash +set -e + +# usage: ./build.sh [clean|clean --confirm|skiptest] + BUILD_DIR=cmake-build -# Choose: Debug, Release, RelWithDebInfo and MinSizeRel +# Choose: Debug, Release, RelWithDebInfo and MinSizeRel. Use Debug for asan checking locally. BUILD_TYPE=Debug BLUE="\033[0;34m" @@ -11,43 +15,49 @@ if [[ "$1" == "clean" ]]; then echo -e "${BLUE}==== clean ====${NC}" rm -rf $BUILD_DIR rm -f spectator/*.inc - if [[ "$2" == "--force" ]]; then - # remove all packages and binaries from the local cache, to allow swapping between Debug/Release builds - conan remove '*' --force + if [[ "$2" == "--confirm" ]]; then + # remove all packages from the conan cache, to allow swapping between Release/Debug builds + conan remove "*" --confirm fi fi if [[ "$OSTYPE" == "linux-gnu"* ]]; then - export CC=gcc-11 - export CXX=g++-11 + export CC=gcc-13 + export CXX=g++-13 fi -if [[ ! -d $BUILD_DIR ]]; then - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - echo -e "${BLUE}==== configure default profile ====${NC}" - conan profile new default --detect - conan profile update settings.compiler.libcxx=libstdc++11 default - fi +if [[ ! -f "$HOME/.conan2/profiles/default" ]]; then + echo -e "${BLUE}==== create default profile ====${NC}" + conan profile detect +fi +if [[ ! -d $BUILD_DIR ]]; then echo -e "${BLUE}==== install required dependencies ====${NC}" if [[ "$BUILD_TYPE" == "Debug" ]]; then - conan install . --build --install-folder $BUILD_DIR --profile ./sanitized + conan install . --output-folder=$BUILD_DIR --build="*" --settings=build_type=$BUILD_TYPE --profile=./sanitized else - conan install . --build=missing --install-folder $BUILD_DIR + conan install . --output-folder=$BUILD_DIR --build=missing fi fi -pushd $BUILD_DIR || exit 1 +pushd $BUILD_DIR + +echo -e "${BLUE}==== configure conan environment to access tools ====${NC}" +source conanbuild.sh + +if [[ $OSTYPE == "darwin"* ]]; then + export MallocNanoZone=0 +fi echo -e "${BLUE}==== generate build files ====${NC}" -cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE || exit 1 +cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE echo -e "${BLUE}==== build ====${NC}" -cmake --build . || exit 1 +cmake --build . if [[ "$1" != "skiptest" ]]; then echo -e "${BLUE}==== test ====${NC}" GTEST_COLOR=1 ctest --verbose fi -popd || exit 1 +popd diff --git a/conanfile.py b/conanfile.py index 3a021ff..66d7cd6 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,15 +1,15 @@ -from conans import ConanFile +from conan import ConanFile class SpectatorCppConan(ConanFile): settings = "os", "compiler", "build_type", "arch" requires = ( - "abseil/20230125.3", - "asio/1.28.1", + "abseil/20240722.0", + "asio/1.32.0", "backward-cpp/1.6", - "fmt/10.1.1", - "gtest/1.14.0", - "spdlog/1.12.0" + "fmt/11.0.2", + "gtest/1.15.0", + "spdlog/1.15.0", ) - generators = "cmake" - default_options = {} + tool_requires = () + generators = "CMakeDeps", "CMakeToolchain" diff --git a/requirements.txt b/requirements.txt index 2ce40bf..5b02ca9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -conan==1.64.1 +conan==2.9.2 diff --git a/spectator/id.h b/spectator/id.h index 1f351af..355f086 100644 --- a/spectator/id.h +++ b/spectator/id.h @@ -186,16 +186,39 @@ struct equal_to> { } // namespace std -template <> -struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); +template <> struct fmt::formatter: formatter { + auto format(const spectator::Tags& tags, format_context& ctx) const -> format_context::iterator { + std::string s; + auto size = tags.size(); + + if (size > 0) { + // sort keys, to ensure stable output + std::vector keys; + for (const auto& pair : tags) { + keys.push_back(pair.first); + } + std::sort(keys.begin(), keys.end()); + + s = "["; + for (const auto &key : keys) { + if (size > 1) { + s += key + "=" + tags.at(key) + ", "; + } else { + s += key + "=" + tags.at(key) + "]"; + } + size -= 1; + } + } else { + s = "[]"; + } + + return fmt::formatter::format(s, ctx); } +}; - // formatter for Ids - template - auto format(const spectator::Id& id, FormatContext& context) { - return fmt::format_to(context.out(), "Id(name={}, tags={})", id.Name(), - id.GetTags()); +template <> struct fmt::formatter: formatter { + auto format(const spectator::Id& id, format_context& ctx) const -> format_context::iterator { + auto s = fmt::format("Id(name={}, tags={})", id.Name(), id.GetTags()); + return fmt::formatter::format(s, ctx); } }; diff --git a/spectator/id_test.cc b/spectator/id_test.cc index 5e03976..6ac8baf 100644 --- a/spectator/id_test.cc +++ b/spectator/id_test.cc @@ -2,6 +2,7 @@ #include namespace { + using spectator::Id; using spectator::Tags; @@ -9,15 +10,23 @@ TEST(Id, Create) { Id id{"foo", Tags{}}; EXPECT_EQ(id.Name(), "foo"); EXPECT_EQ(id.GetTags().size(), 0); + EXPECT_EQ(fmt::format("{}", id), "Id(name=foo, tags=[])"); + + Id id_tags_single{"name", Tags{{"k", "v"}}}; + EXPECT_EQ(id_tags_single.Name(), "name"); + EXPECT_EQ(id_tags_single.GetTags().size(), 1); + EXPECT_EQ(fmt::format("{}", id_tags_single), "Id(name=name, tags=[k=v])"); + + Id id_tags_multiple{"name", Tags{{"k", "v"}, {"k1", "v1"}}}; + EXPECT_EQ(id_tags_multiple.Name(), "name"); + EXPECT_EQ(id_tags_multiple.GetTags().size(), 2); - Id id_tags{"name", Tags{{"k", "v"}, {"k1", "v1"}}}; - EXPECT_EQ(id_tags.Name(), "name"); - EXPECT_EQ(id_tags.GetTags().size(), 2); + EXPECT_EQ(fmt::format("{}", id_tags_multiple), "Id(name=name, tags=[k=v, k1=v1])"); std::shared_ptr id_of{Id::of("name", Tags{{"k", "v"}, {"k1", "v1"}})}; EXPECT_EQ(id_of->Name(), "name"); EXPECT_EQ(id_of->GetTags().size(), 2); - fmt::format("{}", id); + EXPECT_EQ(fmt::format("{}", *id_of), "Id(name=name, tags=[k=v, k1=v1])"); } TEST(Id, Tags) { diff --git a/spectator/meter_type.h b/spectator/meter_type.h index 3a8d565..5b2329c 100644 --- a/spectator/meter_type.h +++ b/spectator/meter_type.h @@ -17,13 +17,11 @@ enum class MeterType { }; } -// Provide a formatter for MeterType -template <> -struct fmt::formatter : fmt::formatter { - template - auto format(const spectator::MeterType& meter_type, FormatContext& context) { +template <> struct fmt::formatter: formatter { + auto format(spectator::MeterType meter_type, format_context& ctx) const -> format_context::iterator { using namespace spectator; std::string_view s = "unknown"; + switch (meter_type) { case MeterType::AgeGauge: s = "age-gauge"; @@ -56,6 +54,7 @@ struct fmt::formatter : fmt::formatter { s = "timer"; break; } - return fmt::formatter::format(s, context); + + return fmt::formatter::format(s, ctx); } }; diff --git a/spectator/meter_type_test.cc b/spectator/meter_type_test.cc new file mode 100644 index 0000000..0a8e318 --- /dev/null +++ b/spectator/meter_type_test.cc @@ -0,0 +1,11 @@ +#include "../spectator/meter_type.h" +#include + +namespace { + +using spectator::MeterType; + +TEST(MeterType, Format) { + EXPECT_EQ(fmt::format("{}", MeterType::Counter), "counter"); +} +} // namespace diff --git a/spectator/stateless_meters.h b/spectator/stateless_meters.h index df12eed..3e56162 100644 --- a/spectator/stateless_meters.h +++ b/spectator/stateless_meters.h @@ -1,5 +1,6 @@ #pragma once #include "id.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/time/time.h"