From ab444bb313e5bcbcdf54f247a20910897cfd92d2 Mon Sep 17 00:00:00 2001 From: Arniiiii Date: Mon, 23 Sep 2024 15:24:01 +0000 Subject: [PATCH] apply ModernCppStarter because I feel exhausted maintaining previous template --- .github/workflows/documentation.yaml | 37 ++ .github/workflows/install.yml | 44 ++ .github/workflows/install_download_all.yml | 45 ++ .github/workflows/macos.yml | 63 +-- .github/workflows/release.yml | 189 ------- .github/workflows/standalone.yml | 35 ++ .github/workflows/style.yml | 35 ++ .github/workflows/ubuntu.yml | 69 ++- .github/workflows/windows.yml | 60 +-- CMakeLists.txt | 413 ++++----------- CONTRIBUTING.md | 203 -------- PULL_REQUEST_TEMPLATE.md | 72 --- all/CMakeLists.txt | 15 + cmake/CompilerWarnings.cmake | 100 ---- cmake/Conan.cmake | 46 -- cmake/Doxygen.cmake | 11 - cmake/SourcesAndHeaders.cmake | 17 - cmake/StandardSettings.cmake | 124 ----- cmake/StaticAnalyzers.cmake | 20 - cmake/Utils.cmake | 40 -- cmake/Vcpkg.cmake | 20 - cmake/fmtlogConfig.cmake.in | 18 - cmake/{get_CPM.cmake => getCPM.cmake} | 0 cmake/optimization_flags/lto.cmake | 43 ++ cmake/optimization_flags/polyhedral.cmake | 43 ++ cmake/tools.cmake | 66 +++ cmake/version.hpp.in | 11 - conanfile.py | 61 --- conanfile.txt | 6 - include/fmtlog/fmtlog-inl.h | 459 +++++++++++------ include/fmtlog/fmtlog.h | 560 +++++++++++++-------- test/CMakeLists.txt | 132 +++-- 32 files changed, 1235 insertions(+), 1822 deletions(-) create mode 100644 .github/workflows/documentation.yaml create mode 100644 .github/workflows/install.yml create mode 100644 .github/workflows/install_download_all.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/standalone.yml create mode 100644 .github/workflows/style.yml delete mode 100644 CONTRIBUTING.md delete mode 100644 PULL_REQUEST_TEMPLATE.md create mode 100644 all/CMakeLists.txt delete mode 100644 cmake/CompilerWarnings.cmake delete mode 100644 cmake/Conan.cmake delete mode 100644 cmake/Doxygen.cmake delete mode 100644 cmake/SourcesAndHeaders.cmake delete mode 100644 cmake/StandardSettings.cmake delete mode 100644 cmake/StaticAnalyzers.cmake delete mode 100644 cmake/Utils.cmake delete mode 100644 cmake/Vcpkg.cmake delete mode 100644 cmake/fmtlogConfig.cmake.in rename cmake/{get_CPM.cmake => getCPM.cmake} (100%) create mode 100644 cmake/optimization_flags/lto.cmake create mode 100644 cmake/optimization_flags/polyhedral.cmake create mode 100644 cmake/tools.cmake delete mode 100644 cmake/version.hpp.in delete mode 100644 conanfile.py delete mode 100644 conanfile.txt diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml new file mode 100644 index 0000000..9e90bf5 --- /dev/null +++ b/.github/workflows/documentation.yaml @@ -0,0 +1,37 @@ +name: Documentation + +on: + push: + tags: + - "*" + +env: + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + name: Build and publish documentation + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: Install dependencies + run: | + brew install doxygen + pip3 install jinja2 Pygments + + - name: Build + run: | + cmake -Sdocumentation -Bbuild + cmake --build build --target GenerateDocs + + - name: Publish + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./build/doxygen/html diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml new file mode 100644 index 0000000..8dfbece --- /dev/null +++ b/.github/workflows/install.yml @@ -0,0 +1,44 @@ +name: Install + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: build and install library + run: | + cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release --log-level=DEBUG + sudo cmake --build build --target install --verbose -j4 + rm -rf build + + - name: configure + run: cmake -Stest -Bbuild -DTEST_INSTALLED_VERSION=1 -DCPM_DOWNLOAD_ALL=1 -DCPM_SOURCE_CACHE=~/.cache/cpm --log-level=DEBUG + + - name: build + run: cmake --build build --config Debug -j4 --verbose + + - name: test + run: | + cd build + ctest --build-config Debug -j4 diff --git a/.github/workflows/install_download_all.yml b/.github/workflows/install_download_all.yml new file mode 100644 index 0000000..989fee4 --- /dev/null +++ b/.github/workflows/install_download_all.yml @@ -0,0 +1,45 @@ +name: Install_CPM_DOWNLOAD_ALL + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: build and install library + run: | + cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCPM_DOWNLOAD_ALL=1 --log-level=DEBUG + cmake --build build --target Greeter -j4 --verbose + cmake --install ./build --prefix ./install_dir + rm -rf build + + - name: configure + run: CMAKE_PREFIX_PATH="./install_dir" cmake -Stest -Bbuild -DTEST_INSTALLED_VERSION=1 -DCPM_DOWNLOAD_ALL=1 -DCPM_SOURCE_CACHE=~/.cache/cpm --log-level=DEBUG + + - name: build + run: cmake --build build --config Debug -j4 --verbose + + - name: test + run: | + cd build + ctest --build-config Debug -j4 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 64fdb4e..d4b4207 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -2,52 +2,37 @@ name: MacOS on: push: - branches: [ master ] + branches: + - master + - main pull_request: - branches: [ master ] + branches: + - master + - main env: - BUILD_TYPE: Release - INSTALL_LOCATION: .local + CTEST_OUTPUT_ON_FAILURE: 1 + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules jobs: build: - runs-on: macos-latest - if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" steps: - - uses: actions/checkout@v2 - - - name: cache dependencies - uses: actions/cache@v2 - id: cache - with: - path: ${{ github.workspace }}/${{ env.INSTALL_LOCATION }} - key: ${{ runner.os }}-dependencies - - # - name: install GoogleTest - # if: ${{ steps.cache.output.cache-hit != 'true' }} - # run: | - # cd .. - # git clone https://github.com/google/googletest.git --branch release-1.10.0 - # cd googletest - # cmake -Bbuild -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/$INSTALL_LOCATION - # cmake --build build --config Release - # cmake --build build --target install --config Release - # cd ../modern-cpp-template - - - name: configure - run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/$INSTALL_LOCATION - - - name: build - run: cmake --build build --config $BUILD_TYPE -j4 - - - name: run tests - run: | - cd build - ctest -C $BUILD_TYPE -VV - - - name: install project - run: cmake --build build --target install --config Release + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: configure + run: cmake -Stest -Bbuild -DCMAKE_BUILD_TYPE=Debug --log-level=DEBUG + + - name: build + run: cmake --build build -j4 --verbose + - name: test + run: | + cd build + ctest --build-config Debug -j4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 9220413..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,189 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -env: - PROJECT_NAME: "modern-cpp-template" - BUILD_TYPE: Release - -jobs: - build: - name: ${{ matrix.config.name }} - runs-on: ${{ matrix.config.os }} - strategy: - fail-fast: false - matrix: - config: - - { - name: "Windows Latest MSVC", - artifact_ext: '.zip', - os: windows-latest, - cc: "cl", - cxx: "cl", - environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat", - } - - { - name: "Ubuntu Latest GCC", - artifact_ext: '.tar.gz', - os: ubuntu-latest, - cc: "gcc", - cxx: "g++", - } - - { - name: "macOS Latest Clang", - artifact_ext: '.tar.gz', - os: macos-latest, - cc: "clang", - cxx: "clang++", - } - - steps: - - name: set version name (Windows) - id: version_win - if: ${{ runner.os == 'Windows' }} - run: | - $TAG = (${env:GITHUB_REF} -replace 'refs/tags/', '') - echo "::set-output name=name::$TAG" - - - name: set version name - id: version - if: ${{ runner.os != 'Windows' }} - run: echo ::set-output name=name::${GITHUB_REF#refs/tags/} - - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: cache dependencies - uses: actions/cache@v2 - id: cache - with: - path: ${{ github.HOME }}/.local - key: ${{ runner.os }}-dependencies - - # - name: install GoogleTest - # if: ${{ steps.cache.output.cache-hit != 'true' }} - # run: | - # cd .. - # git clone https://github.com/google/googletest.git --branch release-1.10.0 - # cd googletest - # cmake -Bbuild -DCMAKE_INSTALL_PREFIX="$HOME/.local" -Dgtest_force_shared_crt=1 - # cmake --build build --config Release - # cmake --build build --target install --config Release - # cd ../modern-cpp-template - - - name: configure - run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX="$HOME/.local" - - - name: build - run: cmake --build build --config "$env:BUILD_TYPE" -j4 - - - name: run tests - run: | - cd build - ctest -C "$env:BUILD_TYPE" -VV - - # for a release not containing directly the source code, replace the files archived - # with the actual files needed (i.e. *.lib/*.a and *.h(pp)) - - - name: generate archive (Windows) - if: ${{ runner.os == 'Windows' }} - run: | - rmdir -r -fo build - 7z a -tzip $HOME/artifact.zip * - - - - name: generate archive - if: ${{ runner.os != 'Windows' }} - run: | - rm -rf build - tar -cvzf $HOME/artifact.tar.gz . - - - name: upload artifacts - uses: actions/upload-artifact@v2 - if: ${{ runner.os == 'Windows' }} - with: - name: ${{ runner.os }}-${{ steps.version_win.outputs.name }} - path: '~/artifact.*' - - - name: upload artifacts - uses: actions/upload-artifact@v2 - if: ${{ runner.os != 'Windows' }} - with: - name: ${{ runner.os }}-${{ steps.version.outputs.name }} - path: '~/artifact.*' - - release: - if: contains(github.ref, 'tags/v') - runs-on: ubuntu-latest - needs: build - - steps: - - name: set version name - id: version - run: echo ::set-output name=name::${GITHUB_REF#refs/tags/} - - - name: create release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ steps.version.outputs.name }} - # if needed, you can set the release body here - #body: "Release notes" - draft: false - prerelease: false - - - name: download artifact - uses: actions/download-artifact@v2 - with: - name: "Linux-${{ steps.version.outputs.name }}" - path: ./ - - - name: upload ubuntu release asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: "artifact.tar.gz" - asset_name: "${{ env.PROJECT_NAME }}-Linux-${{ steps.version.outputs.name }}.tar.gz" - asset_content_type: application/x-tar - - - name: download artifact - uses: actions/download-artifact@v2 - with: - name: "Windows-${{ steps.version.outputs.name }}" - path: ./ - - - name: upload windows release asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: "artifact.zip" - asset_name: "${{ env.PROJECT_NAME }}-Windows-${{ steps.version.outputs.name }}.zip" - asset_content_type: application/zip - - - name: download artifact - uses: actions/download-artifact@v2 - with: - name: "macOS-${{ steps.version.outputs.name }}" - path: ./ - - - name: upload macos release asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: "./artifact.tar.gz" - asset_name: "${{ env.PROJECT_NAME }}-macOS-${{ steps.version.outputs.name }}.tar.gz" - asset_content_type: application/x-tar diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml new file mode 100644 index 0000000..166588d --- /dev/null +++ b/.github/workflows/standalone.yml @@ -0,0 +1,35 @@ +name: Standalone + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +env: + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: configure + run: cmake -Sstandalone -Bbuild -DCMAKE_BUILD_TYPE=Debug --log-level=DEBUG + + - name: build + run: cmake --build build -j4 --verbose + + - name: run + run: ./build/Greeter 8000 & sleep 5 ; kill $! diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..788d1a7 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,35 @@ +name: Style + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +env: + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: Install format dependencies + run: pip3 install clang-format==14.0.6 cmake_format==0.6.11 pyyaml + + - name: configure + run: cmake -Stest -Bbuild --log-level=DEBUG + + - name: check style + run: cmake --build build --target check-format -j4 --verbose diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1d69204..9719498 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -2,54 +2,41 @@ name: Ubuntu on: push: - branches: [ master ] + branches: + - master + - main pull_request: - branches: [ master ] + branches: + - master + - main env: - BUILD_TYPE: Release - INSTALL_LOCATION: .local + CTEST_OUTPUT_ON_FAILURE: 1 + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules jobs: build: - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" steps: - - uses: actions/checkout@v2 - - # - name: cache dependencies - # uses: actions/cache@v2 - # id: cache - # with: - # path: ${{ github.workspace }}/${{ env.INSTALL_LOCATION }} - # key: ${{ runner.os }}-dependencies - # - # - name: install GoogleTest - # if: ${{ steps.cache.output.cache-hit != 'true' }} - # run: | - # cd .. - # git clone https://github.com/google/googletest.git --branch release-1.10.0 - # cd googletest - # cmake -Bbuild -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/$INSTALL_LOCATION - # cmake --build build --config Release - # cmake --build build --target install --config Release - - - name: configure - run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/$INSTALL_LOCATION -Dfmtlog_ENABLE_CODE_COVERAGE=1 -Dfmtlog_ENABLE_CPM=1 - - - name: build - run: cmake --build build --config $BUILD_TYPE -j4 - - - name: run tests - run: | - cd build - ctest -C $BUILD_TYPE -VV - - # - name: Code coverage using Codecov - # run: bash <(curl -s https://codecov.io/bash) - - - name: install project - run: cmake --build build --target install --config Release + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + - name: configure + run: cmake -Stest -Bbuild -DENABLE_TEST_COVERAGE=1 -DCMAKE_BUILD_TYPE=Debug --log-level=DEBUG + + - name: build + run: cmake --build build -j4 --verbose + + - name: test + run: | + cd build + ctest --build-config Debug -j4 + - name: collect code coverage + run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 17be1c4..7d84be3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -2,52 +2,40 @@ name: Windows on: push: - branches: [ master ] + branches: + - master + - main pull_request: - branches: [ master ] + branches: + - master + - main env: - BUILD_TYPE: Release - INSTALL_LOCATION: ".local" + CTEST_OUTPUT_ON_FAILURE: 1 + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules jobs: build: - runs-on: windows-latest - if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" steps: - - uses: actions/checkout@v2 - - - name: cache dependencies - uses: actions/cache@v2 - id: cache - with: - path: ${{env.INSTALL_LOCATION}} - key: ${{runner.os}}-dependencies - - # - name: install GoogleTest - # if: ${{steps.cache.output.cache-hit != 'true'}} - # run: | - # cd .. - # git clone https://github.com/google/googletest.git --branch release-1.10.0 - # cd googletest - # cmake -Bbuild -DCMAKE_INSTALL_PREFIX="$HOME/$env:INSTALL_LOCATION" -Dgtest_force_shared_crt=1 - # cmake --build build --config Release - # cmake --build build --target install --config Release - # cd ../modern-cpp-template + - uses: actions/checkout@v3 - - name: configure - run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX="$HOME/$env:INSTALL_LOCATION" + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} - - name: build - run: cmake --build build --config "$env:BUILD_TYPE" -j4 - - - name: run tests - run: | - cd build - ctest -C "$env:BUILD_TYPE" -VV + - name: Support longpaths + run: git config --system core.longpaths true # https://github.com/orgs/community/discussions/26952 + + - name: configure + run: cmake -Stest -Bbuild --log-level=DEBUG - - name: install project - run: cmake --build build --target install --config Release + - name: build + run: cmake --build build --config Debug -j4 --verbose + - name: test + run: | + cd build + ctest --build-config Debug -j4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c048af..80e3e2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,358 +1,121 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14...3.22) -# -# Project details -# +# ---- Project ---- +# Note: update this to your new project's name and version +set(fmtlog_VERSION 2.3.0) project( - "fmtlog" - VERSION 2.3.0 + fmtlog + VERSION ${fmtlog_VERSION} LANGUAGES CXX) -set(${PROJECT_NAME}_BUILD_HEADERS_ONLY ON) - -# -# Set project options -# - -include(cmake/StandardSettings.cmake) -include(cmake/StaticAnalyzers.cmake) -include(cmake/Utils.cmake) -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Debug") +if(CMAKE_BUILD_TYPE STREQUAL Debug) + set(default_value_options ON) +else() + set(default_value_options OFF) endif() -message(STATUS "Started CMake for ${PROJECT_NAME} v${PROJECT_VERSION}...\n") -if(UNIX) - add_compile_options("$<$:-D_DEBUG>") # this will allow to use - # same _DEBUG macro available in both Linux as well as Windows - MSCV - # environment. Easy to put Debug specific code. -endif(UNIX) +option(${PROJECT_NAME}_BUILD_WITH_ASAN + "add -fsanitize=address aka turn on ASAN?" ${default_value_options}) +option(BUILD_SHARED_LIBS "yes/no" NO) -# -# Setup alternative names -# - -if(${PROJECT_NAME}_USE_ALT_NAMES) - string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWERCASE) - string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPERCASE) -else() - set(PROJECT_NAME_LOWERCASE ${PROJECT_NAME}) - set(PROJECT_NAME_UPPERCASE ${PROJECT_NAME}) +if(${BUILD_SHARED_LIBS}) + # https://stackoverflow.com/questions/33062728/cmake-link-shared-library-on-windows + if(WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + endif() endif() -# -# Prevent building in the source directory -# +set(FETCHCONTENT_QUIET + OFF + CACHE BOOL "Make downloading of packages quiet") + +# ---- Include guards ---- if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) message( FATAL_ERROR - "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n" + "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there." ) endif() -# -# Enable package managershttps://github.com/filipdutescu/modern-cpp-template -# - -include(cmake/Conan.cmake) -include(cmake/Vcpkg.cmake) - -# -# Create library, setup header and source files -# +# ---- Add source files ---- -# Find all headers and implementation files -include(cmake/SourcesAndHeaders.cmake) +# Note: globbing sources is considered bad practice as CMake's generators may +# not detect new files automatically. Keep that in mind when changing files, or +# explicitly mention them here. +file(GLOB_RECURSE headers CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") +# file(GLOB_RECURSE sources CONFIGURE_DEPENDS +# "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") -if(${PROJECT_NAME}_BUILD_EXECUTABLE) - add_executable(${PROJECT_NAME} ${exe_sources}) - if(${PROJECT_NAME}_VERBOSE_OUTPUT) +# ---- Create library ---- - verbose_message("Found the following sources:") - foreach(source IN LISTS exe_sources) - verbose_message("* ${source}") - endforeach() - endif() - - if(${PROJECT_NAME}_ENABLE_UNIT_TESTING) - add_library(${PROJECT_NAME}_LIB ${headers} ${sources}) +# Note: for header-only libraries change all PUBLIC flags to INTERFACE and +# create an interface target: +add_library(${PROJECT_NAME} INTERFACE) +# add_library(${PROJECT_NAME} ${headers} ${sources}) +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) - if(${PROJECT_NAME}_VERBOSE_OUTPUT) - verbose_message("Found the following headers:") - foreach(header IN LISTS headers) - verbose_message("* ${header}") - endforeach() - endif() - endif() -elseif(${PROJECT_NAME}_BUILD_HEADERS_ONLY) - add_library(${PROJECT_NAME} INTERFACE) +target_include_directories( + ${PROJECT_NAME} INTERFACE $ + $) - if(${PROJECT_NAME}_VERBOSE_OUTPUT) - verbose_message("Found the following headers:") - foreach(header headers) - verbose_message("* ${header}") - endforeach() - endif() -else() - add_library(${PROJECT_NAME} ${headers} ${sources}) +# being a cross-platform target, we enforce standards conformance on MSVC +target_compile_options( + ${PROJECT_NAME} INTERFACE "$<$:/permissive->") - if(${PROJECT_NAME}_VERBOSE_OUTPUT) - verbose_message("Found the following sources:") - foreach(source sources) - verbose_message("* ${source}") - endforeach() - verbose_message("Found the following headers:") - foreach(header headers) - verbose_message("* ${header}") - endforeach() +if(CMAKE_BUILD_TYPE STREQUAL Debug) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${PROJECT_NAME} + INTERFACE -fconcepts-diagnostics-depth=10) endif() -endif() - -set_target_properties( - ${PROJECT_NAME} - PROPERTIES ARCHIVE_OUTPUT_DIRECTORY - "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" - LIBRARY_OUTPUT_DIRECTORY - "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" - RUNTIME_OUTPUT_DIRECTORY - "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}") -if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) - set_target_properties( - ${PROJECT_NAME}_LIB - PROPERTIES ARCHIVE_OUTPUT_DIRECTORY - "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" - LIBRARY_OUTPUT_DIRECTORY - "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" - OUTPUT_NAME ${PROJECT_NAME}) -endif() - -message(STATUS "Added all header and implementation files.\n") - -# -# Set the project standard and warnings -# - -if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) - target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) -else() - target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) - - if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) - target_compile_features(${PROJECT_NAME}_LIB PUBLIC cxx_std_17) + if(${${PROJECT_NAME}_BUILD_WITH_ASAN}) + target_compile_options(${PROJECT_NAME} INTERFACE -fsanitize=address) + target_link_options(${PROJECT_NAME} INTERFACE -fsanitize=address) endif() endif() -include(cmake/CompilerWarnings.cmake) -set_project_warnings(${PROJECT_NAME}) - -verbose_message( - "Applied compiler warnings. Using standard ${CMAKE_CXX_STANDARD}.\n") - -# -# Enable Doxygen -# - -include(cmake/Doxygen.cmake) - -# -# Model project dependencies -# -if(${${PROJECT_NAME}_BUILD_HEADERS_ONLY}) +# ---- Add dependencies via CPM ---- +# see https://github.com/TheLartians/CPM.cmake for more info - if(${${PROJECT_NAME}_ENABLE_CPM}) - include(cmake/get_CPM.cmake) - if(${${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB}) - set(an_inverted_${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB FALSE) - else() - set(an_inverted_${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB TRUE) - endif() - set(fmtlib_VERSION 11.0.2) - cpmaddpackage( - NAME - fmt - VERSION - ${fmtlib_VERSION} - EXCLUDE_FROM_ALL - NO - SYSTEM - YES - URL - "https://github.com/fmtlib/fmt/archive/refs/tags/${fmtlib_VERSION}.tar.gz" - OPTIONS - "FMT_INSTALL YES" - "FMT_MODULE - ${an_inverted_${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB}" - "FMT_OS ${an_inverted_${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB}") - elseif(${${PROJECT_NAME}_ENABLE_VCPKG}) - find_package(fmt CONFIG REQUIRED) - elseif(${${PROJECT_NAME}_ENABLE_CONAN}) - set(${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB OFF) - find_package(fmt REQUIRED) - endif() - - if(${${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB}) - target_link_libraries(${PROJECT_NAME} INTERFACE fmt::fmt-header-only) - else() - target_link_libraries(${PROJECT_NAME} INTERFACE fmt::fmt) - endif() +include(cmake/getCPM.cmake) - find_package(Threads) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/packages/add_fmt.cmake) +target_link_libraries(${PROJECT_NAME} INTERFACE fmt::fmt-header-only) +# ---- Create an installable target ---- +# this allows users to install and find the library via `find_package()`. - target_link_libraries(${PROJECT_NAME} INTERFACE Threads::Threads) - -endif() +include(${CMAKE_CURRENT_LIST_DIR}/cmake/packages/add_packageproject.cmake) -# For Windows, it is necessary to link with the MultiThreaded library. Depending -# on how the rest of the project's dependencies are linked, it might be -# necessary to change the line to statically link with the library. -# -# This is done as follows: -# -# set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -# -# On Linux and Mac this variable is ignored. If any issues rise from it, try -# commenting it out and letting CMake decide how to link with it. -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") +# the location where the project's version header will be placed should match +# the project's regular header paths +string(TOLOWER ${PROJECT_NAME}/version.h VERSION_HEADER_LOCATION) +string(TOLOWER ${PROJECT_NAME}/export.h EXPORT_HEADER_LOCATION) -verbose_message("Successfully added all dependencies and linked against them.") +set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${PROJECT_VERSION}) +set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION ${PROJECT_VERSION}) -# -# Set the build/user include directories -# - -# Allow usage of header files in the `src` directory, but only for utilities -if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) - target_include_directories( - ${PROJECT_NAME} - INTERFACE $ - $) -else() - target_include_directories( - ${PROJECT_NAME} - PUBLIC $ - $ - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) - if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) - target_include_directories( - ${PROJECT_NAME}_LIB - PUBLIC $ - $ - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) - endif() -endif() - -message(STATUS "Finished setting up include directories.") - -# -# Provide alias to library for -# - -if(${PROJECT_NAME}_BUILD_EXECUTABLE) - add_executable(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -else() - add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -endif() - -verbose_message("Project is now aliased as ${PROJECT_NAME}::${PROJECT_NAME}.\n") - -# -# Format the project using the `clang-format` target (i.e: cmake --build build -# --target clang-format) -# - -add_clang_format_target() - -# -# Install library for easy downstream inclusion -# - -include(GNUInstallDirs) -install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}Targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES - DESTINATION include - PUBLIC_HEADER DESTINATION include) - -install( - EXPORT ${PROJECT_NAME}Targets - FILE ${PROJECT_NAME}Targets.cmake - NAMESPACE ${PROJECT_NAME}:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) - -# -# Add version header -# - -configure_file(${CMAKE_CURRENT_LIST_DIR}/cmake/version.hpp.in - include/${PROJECT_NAME_LOWERCASE}/version.hpp @ONLY) - -install( - FILES - ${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME_LOWERCASE}/version.hpp - DESTINATION include/${PROJECT_NAME_LOWERCASE}) - -# -# Install the `include` directory -# - -install(DIRECTORY include/${PROJECT_NAME_LOWERCASE} DESTINATION include) - -verbose_message( - "Install targets successfully built. Install with `cmake --build --target install --config `." -) - -# -# Quick `ConfigVersion.cmake` creation -# - -include(CMakePackageConfigHelpers) -write_basic_package_version_file( - ${PROJECT_NAME}ConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - -configure_package_config_file( - ${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) - -# -# Generate export header if specified -# - -if(${PROJECT_NAME}_GENERATE_EXPORT_HEADER) - include(GenerateExportHeader) - generate_export_header(${PROJECT_NAME}) - install(FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME_LOWERCASE}_export.h - DESTINATION include) - - message( - STATUS - "Generated the export header `${PROJECT_NAME_LOWERCASE}_export.h` and installed it." - ) -endif() - -message(STATUS "Finished building requirements for installing the package.\n") - -# -# Unit testing setup -# - -if(${PROJECT_NAME}_ENABLE_UNIT_TESTING) - enable_testing() - message( - STATUS - "Build unit tests for the project. Tests should always be found in the test folder\n" - ) - add_subdirectory(test) -endif() +packageproject( + NAME + ${PROJECT_NAME} + VERSION + ${PROJECT_VERSION} + NAMESPACE + ${PROJECT_NAME} + BINARY_DIR + ${PROJECT_BINARY_DIR} + INCLUDE_DIR + ${PROJECT_SOURCE_DIR}/include + INCLUDE_DESTINATION + include/${PROJECT_NAME} + VERSION_HEADER + "${VERSION_HEADER_LOCATION}" + EXPORT_HEADER + "${EXPORT_HEADER_LOCATION}" + COMPATIBILITY + "AnyNewerVersion" + DISABLE_VERSION_SUFFIX + ON + DEPENDENCIES + "fmt ${fmt_VERSION}") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 8743dc3..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,203 +0,0 @@ -# Contributing to [INSERT PROJECT NAME] - -The [INSERT PROJECT NAME] team encourages community feedback and contributions. -Thank you for your interest in making [INSERT PROJECT NAME] better! There are several -ways you can get involved. - -If you are looking for a good way to contribute to the project, please: - -* have a look at the [available issue templates](https://github.com/filipdutescu/modern-cpp-template/issues/new/choose) -and checkout the [examples of good first issues](https://github.com/filipdutescu/modern-cpp-template/contribute) -(or [click here](https://github.com/filipdutescu/modern-cpp-template/labels/good%20first%20issue)). - -* look through the [issues that need help](https://github.com/filipdutescu/modern-cpp-template/labels/help%20wanted). - -* take a look at a [Pull Request template](PULL_REQUEST_TEMPLATE.md) to get yourself -started. - -## Reporting issues and suggesting new features - -If you find that the project is not working properly, please file a report using -the [Bug Report template](https://github.com/filipdutescu/modern-cpp-template/issues/new?assignees=&labels=bug&template=bug_report.md&title=[BUG]). -Should the template provided not suit your needs, feel free to make a -[custom Bug Report](https://github.com/filipdutescu/modern-cpp-template/issues/new/choose), -but please label it accordingly. - -We are happy to hear your ideas for how to further improve [INSERT PROJECT NAME], -ensuring it suits your needs. Check the [Issues](https://github.com/filipdutescu/modern-cpp-template/issues) -and see if others have submitted similar feedback. You can upvote existing feedback -(using the thumbs up reaction/by commenting) or [submit a new suggestion](https://github.com/filipdutescu/modern-cpp-template/labels/feature). - -We always look at upvoted items in [Issues](https://github.com/filipdutescu/modern-cpp-template/issues) -when we decide what to work on next. We read the comments and we look forward to -hearing your input. - -## Finding issues you can help with - -Looking for something to work on? -Issues marked [`good first issue`](https://github.com/filipdutescu/modern-cpp-template/labels/good%20first%20issue) -are a good place to start. - -You can also check the [`help wanted`](https://github.com/filipdutescu/modern-cpp-template/labels/help%20wanted) -tag to find other issues to help with. If you're interested in working on a fix, -leave a comment to let everyone know and to help avoid duplicated effort from others. - -## Contributions we accept - -We highly appreciate any contributions that help us improve the end product, with -a high emphasis being put on any bug fixes you can manage to create and direct -improvements which address the top issues reported by Calculator users. Some general -guidelines: - -### DOs - -* **DO** create one pull request per Issue, and ensure that the Issue is linked -in the pull request. You can follow the [Pull Request Template](PULL_REQUEST_TEMPLATE.md) -for this. - -* **DO** follow our [Coding and Style](#style-guidelines) guidelines, and keep code -changes as small as possible. - -* **DO** include corresponding tests whenever possible. - -* **DO** check for additional occurrences of the same problem in other parts of the -codebase before submitting your PR. - -* **DO** link the issue you are addressing in the pull request. - -* **DO** write a good description for your pull request. More detail is better. -Describe *why* the change is being made and *why* you have chosen a particular solution. -Describe any manual testing you performed to validate your change. - -### DO NOTs - -* **DO NOT** merge multiple changes into one PR unless they have the same root cause. -* **DO NOT** merge directly into the master branch. - -> Submitting a pull request for an approved Issue is not a guarantee it will be approved. -> The change must meet our high bar for code quality, architecture and performance. - -## Making changes to the code - -### Preparing your development environment - -To learn how to build the code and run tests, follow the instructions in the [README](README.md). - -### Style guidelines - -The code in this project uses several different coding styles, depending on the -age and history of the code. Please attempt to match the style of surrounding -code as much as possible. In new components, prefer the patterns described in the -[C++ core guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). - -### Code formatting - -***Run clang-format*** - -Use the following commands from the project's root directory to run clang-format -(must be installed on the host system). - -**1. Run the CMake target for `clang-format`:** - -```bash -cmake --build build --target clang-format -``` - -**2. Using clang-format:** - -```bash -# !!! clang-format does not run recursively in subdirectories !!! -# for each .cpp file modified -clang-format -i *.cpp - -# for each .h file modified -clang-format -i *.h - -# for each .hpp file modified -clang-format -i *.hpp -``` - -**3. Using TheLartians' Format.cmake:** - -```bash -cmake -Htest -Bbuild/test - -# view changes -cmake --build build/test --target format - -# apply changes -cmake --build build/test --target fix-format -``` - -See [Format.cmake](https://github.com/TheLartians/Format.cmake) for more options. - -### Testing - -Your change should include tests to verify new functionality wherever possible. -Code should be structured so that it can be unit tested independently of the UI. -Manual test cases should be used where automated testing is not feasible. - -### Git workflow - -The core principle of the project, when it comes to Git workflows is that the -`master` branch should always be in a healthy state which is ready for release. -Every commit on master should be deployable on push. To ensure this, pull request -**must not** be made directly on master. **Each change** should either be made in -the **development branch** (named a variation of development, i.e. `dev`) or in a -separate branch, named as a short summary of the change. - -If your change is complex, please clean up the branch history before submitting a -pull request. You can use [git rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) -to group your changes into a small number of commits which we can review one at a -time. - -When completing a pull request, we will generally squash your changes into a single -commit. After confirming that the change works as intended, the branch *might* be -deleted, in order to prevent branch polluting. Please let us know if your pull request -needs to be merged as separate commits. - -### Continuous Integration - -For this project, CI is provided by [GitHub Actions](https://github.com/features/actions), -with workflows found in the [`.github/workflows` folder](.github/workflows). Workflows -are run automatically on every commit made on the master branch, unless told to skip -for that particular commit. - -To skip CI runs on a particular commit, include either `[skip ci]` or `[ci skip]` -in the commit message. - -```bash -# an example of a commit message that would not trigger CI workflows -git commit -m "my normal commit message [skip ci]" -# or -git commit -m "my normal commit message [ci skip]" -``` - -## Review process - -After submitting a pull request, members of the team will review your code. We will -assign the request to an appropriate reviewer (if applicable). Any member of the -community may participate in the review, but at least one member of the project team -will ultimately approve the request. - -Often, multiple iterations or discussions will be needed to responding to feedback -from reviewers. Try looking at [past pull requests](https://github.com/filipdutescu/modern-cpp-template/pulls?q=is%3Apr+is%3Aclosed) -to see what the experience might be like. - -## Contributor License Agreement - -Before we can review and accept a pull request from you, you'll need to sign a -Contributor License Agreement (CLA). The CLA ensures that the community is free -to use your contributions. Signing the CLA is a manual process, and you need to -do it for each pull request made. This is done by checking the boxes in the -[Pull Request Readiness Checklist of a Pull Request](PULL_REQUEST_TEMPLATE.md#Pull-Request-Readiness-Checklist). - -### IMPORTANT - -***Checking the aforementioned boxes means that you agree to provide your change -and/or code FREE TO USE and SUBJECT TO CHANGES for the entire community!*** - -You don't need to sign a CLA until you're ready to create a pull request. When your -pull request is created, it is reviewed by a team member which, if the change is -trivial (i.e. you just fixed a typo) will be labelled as `cla-not-required`. -Otherwise, it's classified as `cla-required`, if not already signed. diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 85a2582..0000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,72 +0,0 @@ -**IMPORTANT: Please do not create a Pull Request without creating an issue first.** - -*Any change needs to be discussed before proceeding. Failure to do so may result -in the rejection of the pull request.* - -Please provide enough information so that others can review your pull request. You -can skip this if you're fixing a typo or adding an app to the Showcase. - -Explain the **details** for making this change. What existing problem does the pull -request solve? - -Ex: - -1. If you "Added a/changed the function to do X", explain why: - - * it is necessary to have a way to do X. - * if there already exists a way, why is your implementation better - -2. If you "Fixed bug/error in X", explain: - - * what was the bug/error (if you already made an issue, please link to it here) - * how does your implementation fix the issue - -### Code style and formatting - -Check the [Contributors Style Guidelines section](CONTRIBUTING.md#Style-guidelines) -for how to write your code and the [Contributors Code Formatting section](CONTRIBUTING.md#Code-formatting) -for how to format your code. - -#### Closing Issues - -Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes -(if such). - ---- - -Fixes #XXXX - -## Proposed changes - -* -* -* - -## Motivation behind changes - -### Test plan - -Demonstrate the code is solid. Example: The exact commands you ran and their output, -screenshots / videos if the pull request changes UI. - -*Make sure tests pass on all of the [relevant CI workflows](https://github.com/filipdutescu/modern-cpp-template/actions).* - -### Pull Request Readiness Checklist - -See details at [CONTRIBUTING.md](https://github.com/filipdutescu/modern-cpp-template/blob/master/CONTRIBUTING.md). - -* [ ] I agree to contribute to the project under [INSERT PROJECT NAME] (Unlicense) -[License](LICENSE). - -* [ ] To the best of my knowledge, the proposed patch is not based on a code under -GPL or other license that is incompatible with [INSERT PROJECT NAME] - -* [ ] The PR is proposed to proper branch - -* [ ] There is reference to original bug report and related work - -* [ ] There is accuracy test, performance test and test data in the repository, -if applicable - -* [ ] The feature is well documented and sample code can be built with the project -CMake diff --git a/all/CMakeLists.txt b/all/CMakeLists.txt new file mode 100644 index 0000000..9f518d2 --- /dev/null +++ b/all/CMakeLists.txt @@ -0,0 +1,15 @@ +# this script adds all subprojects to a single build to allow IDEs understand the full project +# structure. + +cmake_minimum_required(VERSION 3.14...3.22) + +project(fmtlogBuildAll LANGUAGES CXX) + +include(../cmake/tools.cmake) + +# needed to generate test target +enable_testing() + +# add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../standalone ${CMAKE_BINARY_DIR}/standalone) +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../test ${CMAKE_BINARY_DIR}/test) +# add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../documentation ${CMAKE_BINARY_DIR}/documentation) diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake deleted file mode 100644 index 8754044..0000000 --- a/cmake/CompilerWarnings.cmake +++ /dev/null @@ -1,100 +0,0 @@ -# from here: -# -# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Avai -# lable.md -# Courtesy of Jason Turner - -function(set_project_warnings project_name) - set(MSVC_WARNINGS - /W4 # Baseline reasonable warnings - /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss - # of data - /w14254 # 'operator': conversion from 'type1:field_bits' to - # 'type2:field_bits', possible loss of data - /w14263 # 'function': member function does not override any base class - # virtual member function - /w14265 # 'classname': class has virtual functions, but destructor is not - # virtual instances of this class may not be destructed correctly - /w14287 # 'operator': unsigned/negative constant mismatch - /we4289 # nonstandard extension used: 'variable': loop control variable - # declared in the for-loop is used outside the for-loop scope - /w14296 # 'operator': expression is always 'boolean_value' - /w14311 # 'variable': pointer truncation from 'type1' to 'type2' - /w14545 # expression before comma evaluates to a function which is missing - # an argument list - /w14546 # function call before comma missing argument list - /w14547 # 'operator': operator before comma has no effect; expected - # operator with side-effect - /w14549 # 'operator': operator before comma has no effect; did you intend - # 'operator'? - /w14555 # expression has no effect; expected expression with side- effect - /w14619 # pragma warning: there is no warning number 'number' - /w14640 # Enable warning on thread un-safe static member initialization - /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may - # cause unexpected runtime behavior. - /w14905 # wide string literal cast to 'LPSTR' - /w14906 # string literal cast to 'LPWSTR' - /w14928 # illegal copy-initialization; more than one user-defined - # conversion has been implicitly applied - /permissive- # standards conformance mode for MSVC compiler. - ) - - set(CLANG_WARNINGS - -Wall - -Wextra # reasonable and standard - -Wshadow # warn the user if a variable declaration shadows one from a - # parent context - -Wnon-virtual-dtor # warn the user if a class with virtual functions has a - # non-virtual destructor. This helps catch hard to - # track down memory errors - -Wold-style-cast # warn for c-style casts - -Wcast-align # warn for potential performance problem casts - -Wunused # warn on anything being unused - -Woverloaded-virtual # warn if you overload (not override) a virtual - # function - -Wpedantic # warn if non-standard C++ is used - -Wconversion # warn on type conversions that may lose data - -Wsign-conversion # warn on sign conversions - -Wnull-dereference # warn if a null dereference is detected - -Wdouble-promotion # warn if float is implicit promoted to double - -Wformat=2 # warn on security issues around functions that format output - # (ie printf) - ) - - set(GCC_WARNINGS - ${CLANG_WARNINGS} - -Wmisleading-indentation # warn if indentation implies blocks where blocks - # do not exist - -Wduplicated-cond # warn if if / else chain has duplicated conditions - -Wduplicated-branches # warn if if / else branches have duplicated code - -Wlogical-op # warn about logical operations being used where bitwise were - # probably wanted - -Wuseless-cast # warn if you perform a cast to the same type - ) - - if (${${PROJECT_NAME}_WARNINGS_AS_ERRORS}) - set(CLANG_WARNINGS ${GCC_WARNINGS} -Werror) - set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) - set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) - endif() - - if(MSVC) - set(PROJECT_WARNINGS ${MSVC_WARNINGS}) - elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") - set(PROJECT_WARNINGS ${CLANG_WARNINGS}) - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(PROJECT_WARNINGS ${GCC_WARNINGS}) - else() - message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") - endif() - - if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) - target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) - else() - target_compile_options(${project_name} PUBLIC ${PROJECT_WARNINGS}) - endif() - - if(NOT TARGET ${project_name}) - message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") - endif() -endfunction() diff --git a/cmake/Conan.cmake b/cmake/Conan.cmake deleted file mode 100644 index b844e2a..0000000 --- a/cmake/Conan.cmake +++ /dev/null @@ -1,46 +0,0 @@ -if(${PROJECT_NAME}_ENABLE_CONAN) - # - # Setup Conan requires and options here: - # - - set(${PROJECT_NAME}_CONAN_REQUIRES "") - set(${PROJECT_NAME}_CONAN_OPTIONS "") - - # - # If `conan.cmake` (from https://github.com/conan-io/cmake-conan) does not exist, download it. - # - if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") - message( - STATUS - "Downloading conan.cmake from https://github.com/conan-io/cmake-conan..." - ) - file( - DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake" - "${CMAKE_BINARY_DIR}/conan.cmake" - ) - message(STATUS "Cmake-Conan downloaded succesfully.") - endif() - - include(${CMAKE_BINARY_DIR}/conan.cmake) - - conan_add_remote( - NAME bincrafters - URL - https://api.bintray.com/conan/bincrafters/public-conan - ) - - conan_cmake_run( - REQUIRES - ${${PROJECT_NAME}_CONAN_REQUIRES} - OPTIONS - ${${PROJECT_NAME}_CONAN_OPTIONS} - BASIC_SETUP - CMAKE_TARGETS # Individual targets to link to - BUILD - missing - ) - - conan_basic_setup() - - verbose_message("Conan is setup and all requires have been installed.") -endif() diff --git a/cmake/Doxygen.cmake b/cmake/Doxygen.cmake deleted file mode 100644 index bd6fe45..0000000 --- a/cmake/Doxygen.cmake +++ /dev/null @@ -1,11 +0,0 @@ -if(${PROJECT_NAME}_ENABLE_DOXYGEN) - set(DOXYGEN_CALLER_GRAPH YES) - set(DOXYGEN_CALL_GRAPH YES) - set(DOXYGEN_EXTRACT_ALL YES) - set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs) - - find_package(Doxygen REQUIRED dot) - doxygen_add_docs(doxygen-docs ${PROJECT_SOURCE_DIR}) - - verbose_message("Doxygen has been setup and documentation is now available.") -endif() diff --git a/cmake/SourcesAndHeaders.cmake b/cmake/SourcesAndHeaders.cmake deleted file mode 100644 index 46fd8eb..0000000 --- a/cmake/SourcesAndHeaders.cmake +++ /dev/null @@ -1,17 +0,0 @@ -set(sources -) - -set(exe_sources - ${sources} -) - -set(headers - include/fmtlog/fmtlog.h - include/fmtlog/fmtlog-inl.h -) - -set(test_sources - src/enc_dec_test.cc - src/log_test.cc - src/multithread_test.cc -) diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake deleted file mode 100644 index 1758e57..0000000 --- a/cmake/StandardSettings.cmake +++ /dev/null @@ -1,124 +0,0 @@ -# -# Project settings -# - -option(${PROJECT_NAME}_BUILD_EXECUTABLE - "Build the project as an executable, rather than a library." OFF) -option(${PROJECT_NAME}_BUILD_HEADERS_ONLY - "Build the project as a header-only library." OFF) -option( - ${PROJECT_NAME}_USE_ALT_NAMES - "Use alternative names for the project, such as naming the include directory all lowercase." - ON) - -# -# Compiler options -# - -option(${PROJECT_NAME}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." - OFF) - -# -# Package managers -# -# Currently supporting: Conan, Vcpkg, CPM - -option(${PROJECT_NAME}_ENABLE_CONAN - "Enable the Conan package manager for this project." OFF) -option(${PROJECT_NAME}_ENABLE_VCPKG - "Enable the Vcpkg package manager for this project." OFF) -option(${PROJECT_NAME}_ENABLE_CPM - "Enable the CMake's missing package manager for this project." OFF) - -option(${PROJECT_NAME}_USE_HEADER_ONLY_FMTLIB - "Use Header only fmtlib/fmt . With conan, automatically OFF" ON) - -# -# Unit testing -# -# Currently supporting: GoogleTest, Catch2. - -option(${PROJECT_NAME}_ENABLE_UNIT_TESTING - "Enable unit tests for the projects (from the `test` subfolder)." OFF) - -# option(${PROJECT_NAME}_USE_GTEST "Use the GoogleTest project for creating unit -# tests." ON) option(${PROJECT_NAME}_USE_GOOGLE_MOCK "Use the GoogleMock project -# for extending the unit tests." OFF) - -# option(${PROJECT_NAME}_USE_CATCH2 "Use the Catch2 project for creating unit -# tests." OFF) - -# -# Static analyzers -# -# Currently supporting: Clang-Tidy, Cppcheck. - -option(${PROJECT_NAME}_ENABLE_CLANG_TIDY - "Enable static analysis with Clang-Tidy." OFF) -option(${PROJECT_NAME}_ENABLE_CPPCHECK "Enable static analysis with Cppcheck." - OFF) - -# -# Code coverage -# - -option(${PROJECT_NAME}_ENABLE_CODE_COVERAGE "Enable code coverage through GCC." - OFF) - -# -# Doxygen -# - -option(${PROJECT_NAME}_ENABLE_DOXYGEN - "Enable Doxygen documentation builds of source." OFF) - -# -# Miscelanious options -# - -# Generate compile_commands.json for clang based tools -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -option( - ${PROJECT_NAME}_VERBOSE_OUTPUT - "Enable verbose output, allowing for a better understanding of each step taken." - ON) -option(${PROJECT_NAME}_GENERATE_EXPORT_HEADER - "Create a `project_export.h` file containing all exported symbols." OFF) - -# Export all symbols when building a shared library -if(BUILD_SHARED_LIBS) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) - set(CMAKE_CXX_VISIBILITY_PRESET hidden) - set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) -endif() - -option(${PROJECT_NAME}_ENABLE_LTO - "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)." - OFF) -if(${PROJECT_NAME}_ENABLE_LTO) - include(CheckIPOSupported) - check_ipo_supported(RESULT result OUTPUT output) - if(result) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - else() - message(SEND_ERROR "IPO is not supported: ${output}.") - endif() -endif() - -option(${PROJECT_NAME}_ENABLE_CCACHE - "Enable the usage of Ccache, in order to speed up rebuild times." OFF) -if(${${PROJECT_NAME}_ENABLE_CCACHE}) - find_program(CCACHE_FOUND ccache) - if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) - endif() -endif() - -option(${PROJECT_NAME}_ENABLE_ASAN - "Enable Address Sanitize to detect memory error." OFF) -if(${PROJECT_NAME}_ENABLE_ASAN) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake deleted file mode 100644 index 10e4da4..0000000 --- a/cmake/StaticAnalyzers.cmake +++ /dev/null @@ -1,20 +0,0 @@ -if(${PROJECT_NAME}_ENABLE_CLANG_TIDY) - find_program(CLANGTIDY clang-tidy) - if(CLANGTIDY) - set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) - message("Clang-Tidy finished setting up.") - else() - message(SEND_ERROR "Clang-Tidy requested but executable not found.") - endif() -endif() - -if(${PROJECT_NAME}_ENABLE_CPPCHECK) - find_program(CPPCHECK cppcheck) - if(CPPCHECK) - set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --suppress=missingInclude --enable=all - --inline-suppr --inconclusive -i ${CMAKE_SOURCE_DIR}/imgui/lib) - message("Cppcheck finished setting up.") - else() - message(SEND_ERROR "Cppcheck requested but executable not found.") - endif() -endif() diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake deleted file mode 100644 index 754c7cd..0000000 --- a/cmake/Utils.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# -# Print a message only if the `VERBOSE_OUTPUT` option is on -# - -function(verbose_message content) - if(${PROJECT_NAME}_VERBOSE_OUTPUT) - message(STATUS ${content}) - endif() -endfunction() - -# -# Add a target for formating the project using `clang-format` (i.e: cmake --build build --target clang-format) -# - -function(add_clang_format_target) - if(NOT ${PROJECT_NAME}_CLANG_FORMAT_BINARY) - find_program(${PROJECT_NAME}_CLANG_FORMAT_BINARY clang-format) - endif() - - if(${PROJECT_NAME}_CLANG_FORMAT_BINARY) - if(${PROJECT_NAME}_BUILD_EXECUTABLE) - add_custom_target(clang-format - COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} - -i ${exe_sources} ${headers} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - elseif(${PROJECT_NAME}_BUILD_HEADERS_ONLY) - add_custom_target(clang-format - COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} - -i ${headers} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - else() - add_custom_target(clang-format - COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} - -i ${sources} ${headers} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - endif() - - message(STATUS "Format the project using the `clang-format` target (i.e: cmake --build build --target clang-format).\n") - endif() -endfunction() diff --git a/cmake/Vcpkg.cmake b/cmake/Vcpkg.cmake deleted file mode 100644 index 1c13e38..0000000 --- a/cmake/Vcpkg.cmake +++ /dev/null @@ -1,20 +0,0 @@ -if(${PROJECT_NAME}_ENABLE_VCPKG) - # - # If `vcpkg.cmake` (from https://github.com/microsoft/vcpkg) does not exist, download it. - # - if(NOT EXISTS "${CMAKE_BINARY_DIR}/vcpkg.cmake") - message( - STATUS - "Downloading `vcpkg.cmake` from https://github.com/microsoft/vcpkg..." - ) - file(DOWNLOAD "https://github.com/microsoft/vcpkg/raw/master/scripts/buildsystems/vcpkg.cmake" - "${CMAKE_BINARY_DIR}/vcpkg.cmake" - ) - message(STATUS "Vcpkg config downloaded succesfully.") - endif() - - if(${PROJECT_NAME}_VERBOSE_OUTPUT) - set(VCPKG_VERBOSE ON) - endif() - set(CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" "${CMAKE_BINARY_DIR}/vcpkg.cmake") -endif() diff --git a/cmake/fmtlogConfig.cmake.in b/cmake/fmtlogConfig.cmake.in deleted file mode 100644 index 260642d..0000000 --- a/cmake/fmtlogConfig.cmake.in +++ /dev/null @@ -1,18 +0,0 @@ -set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@) - -include(CMakeFindDependencyMacro) - -string(REGEX MATCHALL "[^;]+" SEPARATE_DEPENDENCIES "fmt") - -foreach(dependency ${SEPARATE_DEPENDENCIES}) - string(REPLACE " " ";" args "${dependency}") - find_dependency(${args}) -endforeach() - -@PACKAGE_INIT@ - -set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@CMAKE_INSTALL_INCLUDEDIR@") - -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") - -check_required_components(@PROJECT_NAME@) diff --git a/cmake/get_CPM.cmake b/cmake/getCPM.cmake similarity index 100% rename from cmake/get_CPM.cmake rename to cmake/getCPM.cmake diff --git a/cmake/optimization_flags/lto.cmake b/cmake/optimization_flags/lto.cmake new file mode 100644 index 0000000..356ccf8 --- /dev/null +++ b/cmake/optimization_flags/lto.cmake @@ -0,0 +1,43 @@ +function(set_project_lto_opts project_name) + # if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") message(AUTHOR_WARNING "The project ${PROJECT_NAME} is + # building in debug mode, thus no compiler flags for link time optimization were added.") return() + # endif() + + set(MSVC_LTO # sorry idk + ) + + if(${PROJECT_NAME}_CLANG_LTO_THIN) + set(CLANG_LTO -flto=thin -Werror=odr -Werror=strict-aliasing) + else() + set(CLANG_LTO -flto -Werror=odr -Werror=strict-aliasing) + endif() + + set(GCC_LTO -flto -Werror=odr -Werror=lto-type-mismatch -Werror=strict-aliasing) + + # if(MSVC) set(PROJECT_LTO ${MSVC_LTO}) elseif + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_LTO ${CLANG_LTO}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_LTO ${GCC_LTO}) + else() + message( + AUTHOR_WARNING + "No compiler flags for link time optimization were set for '${CMAKE_CXX_COMPILER_ID}' compiler." + ) + endif() + + if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + target_compile_options(${project_name} INTERFACE ${PROJECT_LTO}) + target_link_options(${project_name} INTERFACE ${PROJECT_LTO}) + else() + target_compile_options(${project_name} PUBLIC ${PROJECT_LTO}) + target_link_options(${project_name} PUBLIC ${PROJECT_LTO}) + endif() + + if(NOT TARGET ${project_name}) + message( + AUTHOR_WARNING + "${project_name} is not a target, thus no compiler flags for link time optimization were added." + ) + endif() +endfunction() diff --git a/cmake/optimization_flags/polyhedral.cmake b/cmake/optimization_flags/polyhedral.cmake new file mode 100644 index 0000000..9d29d30 --- /dev/null +++ b/cmake/optimization_flags/polyhedral.cmake @@ -0,0 +1,43 @@ +function(set_project_polyhedral_opts project_name) + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + message( + AUTHOR_WARNING + "The project ${PROJECT_NAME} is building in debug mode, thus no compiler flags for polyhedral optimization were added." + ) + return() + endif() + + set(MSVC_POLYHEDRAL # sorry idk + ) + + set(CLANG_POLLY -mllvm -polly) + + set(GCC_GRAPHITE -fgraphite-identity -floop-interchange -floop-strip-mine -floop-nest-optimize) + + # if(MSVC) set(PROJECT_POLYHEDRAL ${MSVC_POLYHEDRAL}) elseif + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_POLYHEDRAL ${CLANG_POLLY}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_POLYHEDRAL ${GCC_GRAPHITE}) + else() + message( + AUTHOR_WARNING + "No compiler flags for polyhedral optimization were set for '${CMAKE_CXX_COMPILER_ID}' compiler." + ) + endif() + + if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) + target_compile_options(${project_name} INTERFACE ${PROJECT_POLYHEDRAL}) + target_link_options(${project_name} INTERFACE ${PROJECT_POLYHEDRAL}) + else() + target_compile_options(${project_name} PUBLIC ${PROJECT_POLYHEDRAL}) + target_link_options(${project_name} PUBLIC ${PROJECT_POLYHEDRAL}) + endif() + + if(NOT TARGET ${project_name}) + message( + AUTHOR_WARNING + "${project_name} is not a target, thus no compiler flags for polyhedral optimization were added." + ) + endif() +endfunction() diff --git a/cmake/tools.cmake b/cmake/tools.cmake new file mode 100644 index 0000000..9938dbd --- /dev/null +++ b/cmake/tools.cmake @@ -0,0 +1,66 @@ +# this file contains a list of tools that can be activated and downloaded on-demand each tool is +# enabled during configuration by passing an additional `-DUSE_=` argument to CMake + +# only activate tools for top level project +if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + return() +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/getCPM.cmake) + +# enables sanitizers support using the the `USE_SANITIZER` flag available values are: Address, +# Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined' +if(USE_SANITIZER OR USE_STATIC_ANALYZER) + CPMAddPackage("gh:StableCoder/cmake-scripts@23.04") + + if(USE_SANITIZER) + include(${cmake-scripts_SOURCE_DIR}/sanitizers.cmake) + endif() + + if(USE_STATIC_ANALYZER) + if("clang-tidy" IN_LIST USE_STATIC_ANALYZER) + set(CLANG_TIDY + ON + CACHE INTERNAL "" + ) + else() + set(CLANG_TIDY + OFF + CACHE INTERNAL "" + ) + endif() + if("iwyu" IN_LIST USE_STATIC_ANALYZER) + set(IWYU + ON + CACHE INTERNAL "" + ) + else() + set(IWYU + OFF + CACHE INTERNAL "" + ) + endif() + if("cppcheck" IN_LIST USE_STATIC_ANALYZER) + set(CPPCHECK + ON + CACHE INTERNAL "" + ) + else() + set(CPPCHECK + OFF + CACHE INTERNAL "" + ) + endif() + + include(${cmake-scripts_SOURCE_DIR}/tools.cmake) + + clang_tidy(${CLANG_TIDY_ARGS}) + include_what_you_use(${IWYU_ARGS}) + cppcheck(${CPPCHECK_ARGS}) + endif() +endif() + +# enables CCACHE support through the USE_CCACHE flag possible values are: YES, NO or equivalent +if(USE_CCACHE) + CPMAddPackage("gh:TheLartians/Ccache.cmake@1.2.4") +endif() diff --git a/cmake/version.hpp.in b/cmake/version.hpp.in deleted file mode 100644 index 985bad6..0000000 --- a/cmake/version.hpp.in +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef @PROJECT_NAME_UPPERCASE@_VERSION_H_ -#define @PROJECT_NAME_UPPERCASE@_VERSION_H_ - -#define @PROJECT_NAME_UPPERCASE@_VERSION "@PROJECT_VERSION@" - -#define @PROJECT_NAME_UPPERCASE@_MAJOR_VERSION @PROJECT_VERSION_MAJOR@ -#define @PROJECT_NAME_UPPERCASE@_MINOR_VERSION @PROJECT_VERSION_MINOR@ -#define @PROJECT_NAME_UPPERCASE@_PATCH_VERSION @PROJECT_VERSION_PATCH@ - -#endif // @PROJECT_NAME_UPPERCASE@_VERSION_H_ - diff --git a/conanfile.py b/conanfile.py deleted file mode 100644 index 5cc3a85..0000000 --- a/conanfile.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -from conan import ConanFile -from conan.tools.files import copy -from conan.tools.cmake import CMakeToolchain, cmake_layout, CMake -from conan.tools.build import check_max_cppstd, check_min_cppstd, can_run - - -class SumConan(ConanFile): - name = "fmtlog" - version = "2.3.0" - - license = "MIT" - author = "Meng Rao raomeng1@gmail.com" - url = "https://github.com/MengRao/fmtlog" - description = "fmtlog is a performant fmtlib-style logging library with latency in nanoseconds. " - topics = ("logging", "logs", "log", "fmt", "fmtlib", "libfmt", "fmtlog") - - settings = "os", "arch", "compiler", "build_type" - exports_sources = "CMakeLists.txt", "include/*", "test/*", "cmake/*" - no_copy_source = True - generators = "CMakeToolchain", "CMakeDeps" - - def requirements(self): - self.test_requires("fmt/10.1.1") - - def validate(self): - check_min_cppstd(self, "17") - - def layout(self): - cmake_layout(self) - - def build(self): - cmake = CMake(self) - cmake.configure() - cmake.build() - - def package(self): - cmake = CMake(self) - cmake.install() - - def test(self): - if can_run(self): - cmake = CMake(self) - cmake.configure(variables={"FMTLOG_ENABLE_UNIT_TESTING":"ON"}) - cmake.build() - cmd = "cd " + self.cpp.build.bindir + " && ctest . " - self.run(cmd, env="conanrun") - - def package(self): - # This will also copy the "include" folder - copy(self, "*.h", self.source_folder, self.package_folder) - - def package_info(self): - # For header-only packages, libdirs and bindirs are not used - # so it's necessary to set those as empty. - self.cpp_info.bindirs = [] - self.cpp_info.libdirs = [] - - def package_id(self): - self.info.clear() - diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index 2c6e288..0000000 --- a/conanfile.txt +++ /dev/null @@ -1,6 +0,0 @@ -[requires] -fmt/10.1.1 - -[generators] -CMakeDeps -CMakeToolchain diff --git a/include/fmtlog/fmtlog-inl.h b/include/fmtlog/fmtlog-inl.h index 65c4924..2343122 100644 --- a/include/fmtlog/fmtlog-inl.h +++ b/include/fmtlog/fmtlog-inl.h @@ -21,53 +21,73 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "fmtlog.h" + +#pragma once + +#include +#include #include #include -#include -#include + +#include "fmtlog.h" #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif -#include #include +#include #else #include #include #endif -namespace { -void fmtlogEmptyFun(void*) { -} -} // namespace +namespace +{ + void fmtlogEmptyFun(void*) + { + } +} // namespace template class fmtlogDetailT { -public: + public: // https://github.com/MengRao/str template class Str { - public: + public: static const int Size = SIZE; char s[SIZE]; - Str() {} - Str(const char* p) { *this = *(const Str*)p; } + Str() + { + } + Str(const char* p) + { + *this = *(const Str*)p; + } - char& operator[](int i) { return s[i]; } - char operator[](int i) const { return s[i]; } + char& operator[](int i) + { + return s[i]; + } + char operator[](int i) const + { + return s[i]; + } template - void fromi(T num) { - if constexpr (Size & 1) { + void fromi(T num) + { + if constexpr (Size & 1) + { s[Size - 1] = '0' + (num % 10); num /= 10; } - switch (Size & -2) { + switch (Size & -2) + { case 18: *(uint16_t*)(s + 16) = *(uint16_t*)(digit_pairs + ((num % 100) << 1)); num /= 100; case 16: *(uint16_t*)(s + 14) = *(uint16_t*)(digit_pairs + ((num % 100) << 1)); num /= 100; case 14: *(uint16_t*)(s + 12) = *(uint16_t*)(digit_pairs + ((num % 100) << 1)); num /= 100; @@ -80,20 +100,21 @@ class fmtlogDetailT } } - static constexpr const char* digit_pairs = "00010203040506070809" - "10111213141516171819" - "20212223242526272829" - "30313233343536373839" - "40414243444546474849" - "50515253545556575859" - "60616263646566676869" - "70717273747576777879" - "80818283848586878889" - "90919293949596979899"; + static constexpr const char* digit_pairs = + "00010203040506070809" + "10111213141516171819" + "20212223242526272829" + "30313233343536373839" + "40414243444546474849" + "50515253545556575859" + "60616263646566676869" + "70717273747576777879" + "80818283848586878889" + "90919293949596979899"; }; - fmtlogDetailT() - : flushDelay(3000000000) { + fmtlogDetailT() : flushDelay(3000000000) + { args.reserve(4096); args.resize(parttenArgSize); @@ -112,24 +133,49 @@ class fmtlogDetailT memset(membuf.data(), 0, membuf.capacity()); } - ~fmtlogDetailT() { + ~fmtlogDetailT() + { stopPollingThread(); poll(true); closeLogFile(); } - void setHeaderPattern(std::string pattern) { + void setHeaderPattern(std::string pattern) + { // if (shouldDeallocateHeader) delete[] headerPattern.data(); using namespace fmt::literals; - for (int i = 0; i < parttenArgSize; i++) { + for (int i = 0; i < parttenArgSize; i++) + { reorderIdx[i] = parttenArgSize - 1; } headerPattern = fmtlog::unNameFormat( - pattern, reorderIdx, "a"_a = "", "b"_a = "", "C"_a = "", "Y"_a = "", "m"_a = "", "d"_a = "", - "t"_a = "thread name", "F"_a = "", "f"_a = "", "e"_a = "", "S"_a = "", "M"_a = "", "H"_a = "", - "l"_a = fmtlog::LogLevel(), "s"_a = "fmtlog.cc:123", "g"_a = "/home/raomeng/fmtlog/fmtlog.cc:123", "Ymd"_a = "", - "HMS"_a = "", "HMSe"_a = "", "HMSf"_a = "", "HMSF"_a = "", "YmdHMS"_a = "", "YmdHMSe"_a = "", "YmdHMSf"_a = "", - "YmdHMSF"_a = ""); + pattern, + reorderIdx, + "a"_a = "", + "b"_a = "", + "C"_a = "", + "Y"_a = "", + "m"_a = "", + "d"_a = "", + "t"_a = "thread name", + "F"_a = "", + "f"_a = "", + "e"_a = "", + "S"_a = "", + "M"_a = "", + "H"_a = "", + "l"_a = fmtlog::LogLevel(), + "s"_a = "fmtlog.cc:123", + "g"_a = "/home/raomeng/fmtlog/fmtlog.cc:123", + "Ymd"_a = "", + "HMS"_a = "", + "HMSe"_a = "", + "HMSf"_a = "", + "HMSF"_a = "", + "YmdHMS"_a = "", + "YmdHMSe"_a = "", + "YmdHMSf"_a = "", + "YmdHMSF"_a = ""); // shouldDeallocateHeader = headerPattern.data() != pattern; setArg<0>(fmt::string_view(weekdayName.s, 3)); @@ -148,26 +194,32 @@ class fmtlogDetailT setArg<13>(fmt::string_view(logLevel.s, 3)); setArg<14>(fmt::string_view()); setArg<15>(fmt::string_view()); - setArg<16>(fmt::string_view(year.s, 10)); // Ymd - setArg<17>(fmt::string_view(hour.s, 8)); // HMS - setArg<18>(fmt::string_view(hour.s, 12)); // HMSe - setArg<19>(fmt::string_view(hour.s, 15)); // HMSf - setArg<20>(fmt::string_view(hour.s, 18)); // HMSF - setArg<21>(fmt::string_view(year.s, 19)); // YmdHMS - setArg<22>(fmt::string_view(year.s, 23)); // YmdHMSe - setArg<23>(fmt::string_view(year.s, 26)); // YmdHMSf - setArg<24>(fmt::string_view(year.s, 29)); // YmdHMSF + setArg<16>(fmt::string_view(year.s, 10)); // Ymd + setArg<17>(fmt::string_view(hour.s, 8)); // HMS + setArg<18>(fmt::string_view(hour.s, 12)); // HMSe + setArg<19>(fmt::string_view(hour.s, 15)); // HMSf + setArg<20>(fmt::string_view(hour.s, 18)); // HMSF + setArg<21>(fmt::string_view(year.s, 19)); // YmdHMS + setArg<22>(fmt::string_view(year.s, 23)); // YmdHMSe + setArg<23>(fmt::string_view(year.s, 26)); // YmdHMSf + setArg<24>(fmt::string_view(year.s, 29)); // YmdHMSF } class ThreadBufferDestroyer { - public: - explicit ThreadBufferDestroyer() {} + public: + explicit ThreadBufferDestroyer() + { + } - void threadBufferCreated() {} + void threadBufferCreated() + { + } - ~ThreadBufferDestroyer() { - if (fmtlog::threadBuffer != nullptr) { + ~ThreadBufferDestroyer() + { + if (fmtlog::threadBuffer != nullptr) + { fmtlog::threadBuffer->shouldDeallocate = true; fmtlog::threadBuffer = nullptr; } @@ -178,23 +230,29 @@ class fmtlogDetailT { // Constructor StaticLogInfo(fmtlog::FormatToFn fn, const char* loc, fmtlog::LogLevel level, std::string fmtString) - : formatToFn(fn) - , formatString(fmtString) - , location(loc) - , logLevel(level) - , argIdx(-1) {} + : formatToFn(fn), + formatString(fmtString), + location(loc), + logLevel(level), + argIdx(-1) + { + } - void processLocation() { + void processLocation() + { size_t size = strlen(location); const char* p = location + size; - if (size > 255) { + if (size > 255) + { location = p - 255; } endPos = p - location; const char* base = location; - while (p > location) { + while (p > location) + { char c = *--p; - if (c == '/' || c == '\\') { + if (c == '/' || c == '\\') + { base = p + 1; break; } @@ -202,9 +260,15 @@ class fmtlogDetailT basePos = base - location; } - inline fmt::string_view getBase() { return fmt::string_view(location + basePos, endPos - basePos); } + inline fmt::string_view getBase() + { + return fmt::string_view(location + basePos, endPos - basePos); + } - inline fmt::string_view getLocation() { return fmt::string_view(location, endPos); } + inline fmt::string_view getLocation() + { + return fmt::string_view(location, endPos); + } fmtlog::FormatToFn formatToFn; std::string formatString; @@ -221,7 +285,7 @@ class fmtlogDetailT bool shouldDeallocateHeader = false; FILE* outputFp = nullptr; bool manageFp = false; - size_t fpos = 0; // file position of membuf, used only when manageFp == true + size_t fpos = 0; // file position of membuf, used only when manageFp == true int64_t flushDelay; int64_t nextFlushTime = (std::numeric_limits::max)(); uint32_t flushBufSize = 8 * 1024; @@ -230,8 +294,9 @@ class fmtlogDetailT std::vector threadBuffers; struct HeapNode { - HeapNode(fmtlog::ThreadBuffer* buffer) - : tb(buffer) {} + HeapNode(fmtlog::ThreadBuffer* buffer) : tb(buffer) + { + } fmtlog::ThreadBuffer* tb; const fmtlog::SPSCVarQueueOPT::MsgHeader* header = nullptr; @@ -271,7 +336,8 @@ class fmtlogDetailT volatile bool threadRunning = false; std::thread thr; - void resetDate() { + void resetDate() + { time_t rawtime = fmtlogWrapper<>::impl.tscns.rdns() / 1000000000; struct tm* timeinfo = localtime(&rawtime); timeinfo->tm_sec = timeinfo->tm_min = timeinfo->tm_hour = 0; @@ -279,14 +345,16 @@ class fmtlogDetailT year.fromi(1900 + timeinfo->tm_year); month.fromi(1 + timeinfo->tm_mon); day.fromi(timeinfo->tm_mday); - const char* weekdays[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + const char* weekdays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; weekdayName = weekdays[timeinfo->tm_wday]; - const char* monthNames[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + const char* monthNames[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; monthName = monthNames[timeinfo->tm_mon]; } - void preallocate() { - if (fmtlog::threadBuffer) return; + void preallocate() + { + if (fmtlog::threadBuffer) + return; fmtlog::threadBuffer = new fmtlog::ThreadBuffer(); #ifdef _WIN32 uint32_t tid = static_cast(::GetCurrentThreadId()); @@ -294,7 +362,7 @@ class fmtlogDetailT uint32_t tid = static_cast(::syscall(SYS_gettid)); #endif fmtlog::threadBuffer->nameSize = - fmt::format_to_n(fmtlog::threadBuffer->name, sizeof(fmtlog::threadBuffer->name), "{}", tid).size; + fmt::format_to_n(fmtlog::threadBuffer->name, sizeof(fmtlog::threadBuffer->name), "{}", tid).size; sbc.threadBufferCreated(); std::unique_lock guard(bufferMutex); @@ -302,20 +370,25 @@ class fmtlogDetailT } template - inline void setArg(const T& arg) { + inline void setArg(const T& arg) + { args[reorderIdx[I]] = fmt::detail::make_arg(arg); } template - inline void setArgVal(const T& arg) { + inline void setArgVal(const T& arg) + { fmt::detail::value& value_ = *(fmt::detail::value*)&args[reorderIdx[I]]; value_ = fmt::detail::arg_mapper().map(arg); } - void flushLogFile() { - if (outputFp) { + void flushLogFile() + { + if (outputFp) + { fwrite(membuf.data(), 1, membuf.size(), outputFp); - if (!manageFp) fflush(outputFp); + if (!manageFp) + fflush(outputFp); else fpos += membuf.size(); } @@ -323,43 +396,56 @@ class fmtlogDetailT nextFlushTime = (std::numeric_limits::max)(); } - void closeLogFile() { - if (membuf.size()) flushLogFile(); - if (manageFp) fclose(outputFp); + void closeLogFile() + { + if (membuf.size()) + flushLogFile(); + if (manageFp) + fclose(outputFp); outputFp = nullptr; manageFp = false; } - void startPollingThread(int64_t pollInterval) { + void startPollingThread(int64_t pollInterval) + { stopPollingThread(); threadRunning = true; - thr = std::thread([pollInterval, this]() { - while (threadRunning) { - int64_t before = fmtlogWrapper<>::impl.tscns.rdns(); - poll(false); - int64_t delay = fmtlogWrapper<>::impl.tscns.rdns() - before; - if (delay < pollInterval) { - std::this_thread::sleep_for(std::chrono::nanoseconds(pollInterval - delay)); - } - } - poll(true); - }); + thr = std::thread( + [pollInterval, this]() + { + while (threadRunning) + { + int64_t before = fmtlogWrapper<>::impl.tscns.rdns(); + poll(false); + int64_t delay = fmtlogWrapper<>::impl.tscns.rdns() - before; + if (delay < pollInterval) + { + std::this_thread::sleep_for(std::chrono::nanoseconds(pollInterval - delay)); + } + } + poll(true); + }); } - void stopPollingThread() { - if (!threadRunning) return; + void stopPollingThread() + { + if (!threadRunning) + return; threadRunning = false; - if (thr.joinable()) thr.join(); + if (thr.joinable()) + thr.join(); } - void handleLog(fmt::string_view threadName, const fmtlog::SPSCVarQueueOPT::MsgHeader* header) { + void handleLog(fmt::string_view threadName, const fmtlog::SPSCVarQueueOPT::MsgHeader* header) + { setArgVal<6>(threadName); StaticLogInfo& info = bgLogInfos[header->logId]; const char* data = (const char*)(header + 1); const char* end = (const char*)header + header->size; int64_t tsc = *(int64_t*)data; data += 8; - if (!info.formatToFn) { // log once + if (!info.formatToFn) + { // log once info.location = *(const char**)data; data += 8; info.processLocation(); @@ -373,8 +459,9 @@ class fmtlogDetailT t /= 60; minute.fromi(t % 60); t /= 60; - uint32_t h = t; // hour - if (h > 23) { + uint32_t h = t; // hour + if (h > 23) + { h %= 24; resetDate(); } @@ -387,62 +474,85 @@ class fmtlogDetailT fmtlog::vformat_to(membuf, headerPattern, fmt::basic_format_args(args.data(), parttenArgSize)); size_t bodyPos = membuf.size(); - if (info.formatToFn) { + if (info.formatToFn) + { info.formatToFn(info.formatString, data, membuf, info.argIdx, args); } - else { // log once + else + { // log once membuf.append(fmt::string_view(data, end - data)); } - if (logCB && info.logLevel >= minCBLogLevel) { - logCB(ts, info.logLevel, info.getLocation(), info.basePos, threadName, - fmt::string_view(membuf.data() + headerPos, membuf.size() - headerPos), bodyPos - headerPos, - fpos + headerPos); + if (logCB && info.logLevel >= minCBLogLevel) + { + logCB( + ts, + info.logLevel, + info.getLocation(), + info.basePos, + threadName, + fmt::string_view(membuf.data() + headerPos, membuf.size() - headerPos), + bodyPos - headerPos, + fpos + headerPos); } membuf.push_back('\n'); - if (membuf.size() >= flushBufSize || info.logLevel >= flushLogLevel) { + if (membuf.size() >= flushBufSize || info.logLevel >= flushLogLevel) + { flushLogFile(); } } - void adjustHeap(size_t i) { - while (true) { + void adjustHeap(size_t i) + { + while (true) + { size_t min_i = i; - for (size_t ch = i * 2 + 1, end = std::min(ch + 2, bgThreadBuffers.size()); ch < end; ch++) { + for (size_t ch = i * 2 + 1, end = std::min(ch + 2, bgThreadBuffers.size()); ch < end; ch++) + { auto h_ch = bgThreadBuffers[ch].header; auto h_min = bgThreadBuffers[min_i].header; - if (h_ch && (!h_min || *(int64_t*)(h_ch + 1) < *(int64_t*)(h_min + 1))) min_i = ch; + if (h_ch && (!h_min || *(int64_t*)(h_ch + 1) < *(int64_t*)(h_min + 1))) + min_i = ch; } - if (min_i == i) break; + if (min_i == i) + break; std::swap(bgThreadBuffers[i], bgThreadBuffers[min_i]); i = min_i; } } - void poll(bool forceFlush) { + void poll(bool forceFlush) + { fmtlogWrapper<>::impl.tscns.calibrate(); int64_t tsc = fmtlogWrapper<>::impl.tscns.rdtsc(); - if (logInfos.size()) { + if (logInfos.size()) + { std::unique_lock lock(logInfoMutex); - for (auto& info : logInfos) { + for (auto& info : logInfos) + { info.processLocation(); } bgLogInfos.insert(bgLogInfos.end(), logInfos.begin(), logInfos.end()); logInfos.clear(); } - if (threadBuffers.size()) { + if (threadBuffers.size()) + { std::unique_lock lock(bufferMutex); - for (auto tb : threadBuffers) { + for (auto tb : threadBuffers) + { bgThreadBuffers.emplace_back(tb); } threadBuffers.clear(); } - for (size_t i = 0; i < bgThreadBuffers.size(); i++) { + for (size_t i = 0; i < bgThreadBuffers.size(); i++) + { auto& node = bgThreadBuffers[i]; - if (node.header) continue; + if (node.header) + continue; node.header = node.tb->varq.front(); - if (node.tb->shouldDeallocate) { + if (node.tb->shouldDeallocate) + { delete node.tb; node = bgThreadBuffers.back(); bgThreadBuffers.pop_back(); @@ -450,16 +560,20 @@ class fmtlogDetailT } } - if (bgThreadBuffers.empty()) return; + if (bgThreadBuffers.empty()) + return; // build heap - for (int i = bgThreadBuffers.size() / 2; i >= 0; i--) { + for (int i = bgThreadBuffers.size() / 2; i >= 0; i--) + { adjustHeap(i); } - while (true) { + while (true) + { auto h = bgThreadBuffers[0].header; - if (!h || h->logId >= bgLogInfos.size() || *(int64_t*)(h + 1) >= tsc) break; + if (!h || h->logId >= bgLogInfos.size() || *(int64_t*)(h + 1) >= tsc) + break; auto tb = bgThreadBuffers[0].tb; handleLog(fmt::string_view(tb->name, tb->nameSize), h); tb->varq.pop(); @@ -467,16 +581,20 @@ class fmtlogDetailT adjustHeap(0); } - if (membuf.size() == 0) return; - if (!manageFp || forceFlush) { + if (membuf.size() == 0) + return; + if (!manageFp || forceFlush) + { flushLogFile(); return; } int64_t now = fmtlogWrapper<>::impl.tscns.tsc2ns(tsc); - if (now > nextFlushTime) { + if (now > nextFlushTime) + { flushLogFile(); } - else if (nextFlushTime == (std::numeric_limits::max)()) { + else if (nextFlushTime == (std::numeric_limits::max)()) + { nextFlushTime = now + flushDelay; } } @@ -487,65 +605,80 @@ thread_local typename fmtlogDetailT<_>::ThreadBufferDestroyer fmtlogDetailT<_>:: template struct fmtlogDetailWrapper -{ static fmtlogDetailT<> impl; }; +{ + static fmtlogDetailT<> impl; +}; template fmtlogDetailT<> fmtlogDetailWrapper<_>::impl; template -void fmtlogT<_>::registerLogInfo(uint32_t& logId, FormatToFn fn, const char* location, - LogLevel level, std::string fmtString) noexcept { +void fmtlogT<_>::registerLogInfo( + uint32_t& logId, + FormatToFn fn, + const char* location, + LogLevel level, + std::string fmtString) noexcept +{ auto& d = fmtlogDetailWrapper<>::impl; std::lock_guard lock(d.logInfoMutex); - if (logId) return; + if (logId) + return; logId = d.logInfos.size() + d.bgLogInfos.size(); d.logInfos.emplace_back(fn, location, level, fmtString); } template -void fmtlogT<_>::vformat_to(fmtlog::MemoryBuffer& out, fmt::string_view fmt, - fmt::format_args args) { +void fmtlogT<_>::vformat_to(fmtlog::MemoryBuffer& out, fmt::string_view fmt, fmt::format_args args) +{ fmt::detail::vformat_to(out, fmt, args); } template -size_t fmtlogT<_>::formatted_size(fmt::string_view fmt, fmt::format_args args) { +size_t fmtlogT<_>::formatted_size(fmt::string_view fmt, fmt::format_args args) +{ auto buf = fmt::detail::counting_buffer<>(); fmt::detail::vformat_to(buf, fmt, args); return buf.count(); } template -void fmtlogT<_>::vformat_to(char* out, fmt::string_view fmt, fmt::format_args args) { +void fmtlogT<_>::vformat_to(char* out, fmt::string_view fmt, fmt::format_args args) +{ fmt::vformat_to(out, fmt, args); } template -typename fmtlogT<_>::SPSCVarQueueOPT::MsgHeader* fmtlogT<_>::allocMsg(uint32_t size, - bool q_full_cb) noexcept { +typename fmtlogT<_>::SPSCVarQueueOPT::MsgHeader* fmtlogT<_>::allocMsg(uint32_t size, bool q_full_cb) noexcept +{ auto& d = fmtlogDetailWrapper<>::impl; - if (threadBuffer == nullptr) preallocate(); + if (threadBuffer == nullptr) + preallocate(); auto ret = threadBuffer->varq.alloc(size); - if ((ret == nullptr) & q_full_cb) d.logQFullCB(d.logQFullCBArg); + if ((ret == nullptr) & q_full_cb) + d.logQFullCB(d.logQFullCBArg); return ret; } template -typename fmtlogT<_>::SPSCVarQueueOPT::MsgHeader* -fmtlogT<_>::SPSCVarQueueOPT::allocMsg(uint32_t size) noexcept { +typename fmtlogT<_>::SPSCVarQueueOPT::MsgHeader* fmtlogT<_>::SPSCVarQueueOPT::allocMsg(uint32_t size) noexcept +{ return alloc(size); } template -void fmtlogT<_>::preallocate() noexcept { +void fmtlogT<_>::preallocate() noexcept +{ fmtlogDetailWrapper<>::impl.preallocate(); } template -void fmtlogT<_>::setLogFile(const char* filename, bool truncate) { +void fmtlogT<_>::setLogFile(const char* filename, bool truncate) +{ auto& d = fmtlogDetailWrapper<>::impl; FILE* newFp = fopen(filename, truncate ? "w" : "a"); - if (!newFp) { + if (!newFp) + { std::string err = fmt::format("Unable to open file: {}: {}", filename, strerror(errno)); fmt::throw_format_error(err.c_str()); } @@ -558,10 +691,12 @@ void fmtlogT<_>::setLogFile(const char* filename, bool truncate) { } template -void fmtlogT<_>::setLogFile(FILE* fp, bool manageFp) { +void fmtlogT<_>::setLogFile(FILE* fp, bool manageFp) +{ auto& d = fmtlogDetailWrapper<>::impl; closeLogFile(); - if (manageFp) { + if (manageFp) + { setbuf(fp, nullptr); d.fpos = ftell(fp); } @@ -572,64 +707,74 @@ void fmtlogT<_>::setLogFile(FILE* fp, bool manageFp) { } template -void fmtlogT<_>::setFlushDelay(int64_t ns) noexcept { +void fmtlogT<_>::setFlushDelay(int64_t ns) noexcept +{ fmtlogDetailWrapper<>::impl.flushDelay = ns; } template -void fmtlogT<_>::flushOn(LogLevel flushLogLevel) noexcept { +void fmtlogT<_>::flushOn(LogLevel flushLogLevel) noexcept +{ fmtlogDetailWrapper<>::impl.flushLogLevel = flushLogLevel; } template -void fmtlogT<_>::setFlushBufSize(uint32_t bytes) noexcept { +void fmtlogT<_>::setFlushBufSize(uint32_t bytes) noexcept +{ fmtlogDetailWrapper<>::impl.flushBufSize = bytes; } template -void fmtlogT<_>::closeLogFile() noexcept { +void fmtlogT<_>::closeLogFile() noexcept +{ fmtlogDetailWrapper<>::impl.closeLogFile(); } template -void fmtlogT<_>::poll(bool forceFlush) { +void fmtlogT<_>::poll(bool forceFlush) +{ fmtlogDetailWrapper<>::impl.poll(forceFlush); } template -void fmtlogT<_>::setThreadName(const char* name) noexcept { +void fmtlogT<_>::setThreadName(const char* name) noexcept +{ preallocate(); threadBuffer->nameSize = fmt::format_to_n(threadBuffer->name, sizeof(fmtlog::threadBuffer->name), "{}", name).size; } template -void fmtlogT<_>::setLogCB(LogCBFn cb, LogLevel minCBLogLevel_) noexcept { +void fmtlogT<_>::setLogCB(LogCBFn cb, LogLevel minCBLogLevel_) noexcept +{ auto& d = fmtlogDetailWrapper<>::impl; d.logCB = cb; d.minCBLogLevel = minCBLogLevel_; } template -void fmtlogT<_>::setLogQFullCB(LogQFullCBFn cb, void* userData) noexcept { +void fmtlogT<_>::setLogQFullCB(LogQFullCBFn cb, void* userData) noexcept +{ auto& d = fmtlogDetailWrapper<>::impl; d.logQFullCB = cb; d.logQFullCBArg = userData; } template -void fmtlogT<_>::setHeaderPattern(const char* pattern) { +void fmtlogT<_>::setHeaderPattern(const char* pattern) +{ fmtlogDetailWrapper<>::impl.setHeaderPattern(pattern); } template -void fmtlogT<_>::startPollingThread(int64_t pollInterval) noexcept { +void fmtlogT<_>::startPollingThread(int64_t pollInterval) noexcept +{ fmtlogDetailWrapper<>::impl.startPollingThread(pollInterval); } template -void fmtlogT<_>::stopPollingThread() noexcept { +void fmtlogT<_>::stopPollingThread() noexcept +{ fmtlogDetailWrapper<>::impl.stopPollingThread(); } template class fmtlogT<0>; - diff --git a/include/fmtlog/fmtlog.h b/include/fmtlog/fmtlog.h index 6247188..9c82f68 100644 --- a/include/fmtlog/fmtlog.h +++ b/include/fmtlog/fmtlog.h @@ -22,15 +22,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once -//#define FMT_HEADER_ONLY -#include "fmt/format.h" +// #define FMT_HEADER_ONLY #include -#include -#include -#include #include -#include +#include #include +#include +#include +#include + +#include "fmt/format.h" #ifdef _MSC_VER #include @@ -62,37 +63,50 @@ SOFTWARE. #define FMTLOG_QUEUE_SIZE (1 << 20) #endif -namespace fmtlogdetail { -template -struct UnrefPtr : std::false_type -{ using type = Arg; }; +namespace fmtlogdetail +{ + template + struct UnrefPtr : std::false_type + { + using type = Arg; + }; -template<> -struct UnrefPtr : std::false_type -{ using type = char*; }; + template<> + struct UnrefPtr : std::false_type + { + using type = char*; + }; -template<> -struct UnrefPtr : std::false_type -{ using type = void*; }; + template<> + struct UnrefPtr : std::false_type + { + using type = void*; + }; -template -struct UnrefPtr> : std::true_type -{ using type = Arg; }; + template + struct UnrefPtr> : std::true_type + { + using type = Arg; + }; -template -struct UnrefPtr> : std::true_type -{ using type = Arg; }; + template + struct UnrefPtr> : std::true_type + { + using type = Arg; + }; -template -struct UnrefPtr : std::true_type -{ using type = Arg; }; + template + struct UnrefPtr : std::true_type + { + using type = Arg; + }; -}; // namespace fmtlogdetail +}; // namespace fmtlogdetail template class fmtlogT { -public: + public: enum LogLevel : uint8_t { DBG = 0, @@ -136,9 +150,15 @@ class fmtlogT // msg: full log msg with header // bodyPos: log body index in the msg // logFilePos: log file position of this msg - typedef void (*LogCBFn)(int64_t ns, LogLevel level, fmt::string_view location, size_t basePos, - fmt::string_view threadName, fmt::string_view msg, size_t bodyPos, - size_t logFilePos); + typedef void (*LogCBFn)( + int64_t ns, + LogLevel level, + fmt::string_view location, + size_t basePos, + fmt::string_view threadName, + fmt::string_view msg, + size_t bodyPos, + size_t logFilePos); // Set a callback function for all log msgs with a mininum log level static void setLogCB(LogCBFn cb, LogLevel minCBLogLevel) noexcept; @@ -175,10 +195,13 @@ class fmtlogT // https://github.com/MengRao/SPSC_Queue class SPSCVarQueueOPT { - public: + public: struct MsgHeader { - inline void push(uint32_t sz) { *(volatile uint32_t*)&size = sz + sizeof(MsgHeader); } + inline void push(uint32_t sz) + { + *(volatile uint32_t*)&size = sz + sizeof(MsgHeader); + } uint32_t size; uint32_t logId; @@ -187,24 +210,30 @@ class fmtlogT MsgHeader* allocMsg(uint32_t size) noexcept; - MsgHeader* alloc(uint32_t size) { + MsgHeader* alloc(uint32_t size) + { size += sizeof(MsgHeader); uint32_t blk_sz = (size + sizeof(MsgHeader) - 1) / sizeof(MsgHeader); - if (blk_sz >= free_write_cnt) { + if (blk_sz >= free_write_cnt) + { uint32_t read_idx_cache = *(volatile uint32_t*)&read_idx; - if (read_idx_cache <= write_idx) { + if (read_idx_cache <= write_idx) + { free_write_cnt = BLK_CNT - write_idx; - if (blk_sz >= free_write_cnt && read_idx_cache != 0) { // wrap around + if (blk_sz >= free_write_cnt && read_idx_cache != 0) + { // wrap around blk[0].size = 0; blk[write_idx].size = 1; write_idx = 0; free_write_cnt = read_idx_cache; } } - else { + else + { free_write_cnt = read_idx_cache - write_idx; } - if (free_write_cnt <= blk_sz) { + if (free_write_cnt <= blk_sz) + { return nullptr; } } @@ -215,22 +244,26 @@ class fmtlogT return ret; } - inline const MsgHeader* front() { + inline const MsgHeader* front() + { uint32_t size = blk[read_idx].size; - if (size == 1) { // wrap around + if (size == 1) + { // wrap around read_idx = 0; size = blk[0].size; } - if (size == 0) return nullptr; + if (size == 0) + return nullptr; return &blk[read_idx]; } - inline void pop() { + inline void pop() + { uint32_t blk_sz = (blk[read_idx].size + sizeof(MsgHeader) - 1) / sizeof(MsgHeader); *(volatile uint32_t*)&read_idx = read_idx + blk_sz; } - private: + private: alignas(64) MsgHeader blk[BLK_CNT] = {}; uint32_t write_idx = 0; uint32_t free_write_cnt = BLK_CNT; @@ -249,35 +282,39 @@ class fmtlogT // https://github.com/MengRao/tscns class TSCNS { - public: + public: static const int64_t NsPerSec = 1000000000; - void init(int64_t init_calibrate_ns = 20000000, int64_t calibrate_interval_ns = 3 * NsPerSec) { + void init(int64_t init_calibrate_ns = 20000000, int64_t calibrate_interval_ns = 3 * NsPerSec) + { calibate_interval_ns_ = calibrate_interval_ns; int64_t base_tsc, base_ns; syncTime(base_tsc, base_ns); int64_t expire_ns = base_ns + init_calibrate_ns; - while (rdsysns() < expire_ns) std::this_thread::yield(); + while (rdsysns() < expire_ns) + std::this_thread::yield(); int64_t delayed_tsc, delayed_ns; syncTime(delayed_tsc, delayed_ns); double init_ns_per_tsc = (double)(delayed_ns - base_ns) / (delayed_tsc - base_tsc); saveParam(base_tsc, base_ns, base_ns, init_ns_per_tsc); } - void calibrate() { - if (rdtsc() < next_calibrate_tsc_) return; + void calibrate() + { + if (rdtsc() < next_calibrate_tsc_) + return; int64_t tsc, ns; syncTime(tsc, ns); int64_t calulated_ns = tsc2ns(tsc); int64_t ns_err = calulated_ns - ns; int64_t expected_err_at_next_calibration = - ns_err + (ns_err - base_ns_err_) * calibate_interval_ns_ / (ns - base_ns_ + base_ns_err_); - double new_ns_per_tsc = - ns_per_tsc_ * (1.0 - (double)expected_err_at_next_calibration / calibate_interval_ns_); + ns_err + (ns_err - base_ns_err_) * calibate_interval_ns_ / (ns - base_ns_ + base_ns_err_); + double new_ns_per_tsc = ns_per_tsc_ * (1.0 - (double)expected_err_at_next_calibration / calibate_interval_ns_); saveParam(tsc, calulated_ns, ns, new_ns_per_tsc); } - static inline int64_t rdtsc() { + static inline int64_t rdtsc() + { #ifdef _MSC_VER return __rdtsc(); #elif defined(__i386__) || defined(__x86_64__) || defined(__amd64__) @@ -287,31 +324,42 @@ class fmtlogT #endif } - inline int64_t tsc2ns(int64_t tsc) const { - while (true) { + inline int64_t tsc2ns(int64_t tsc) const + { + while (true) + { uint32_t before_seq = param_seq_.load(std::memory_order_acquire) & ~1; std::atomic_signal_fence(std::memory_order_acq_rel); int64_t ns = base_ns_ + (int64_t)((tsc - base_tsc_) * ns_per_tsc_); std::atomic_signal_fence(std::memory_order_acq_rel); uint32_t after_seq = param_seq_.load(std::memory_order_acquire); - if (before_seq == after_seq) return ns; + if (before_seq == after_seq) + return ns; } } - inline int64_t rdns() const { return tsc2ns(rdtsc()); } + inline int64_t rdns() const + { + return tsc2ns(rdtsc()); + } - static inline int64_t rdsysns() { + static inline int64_t rdsysns() + { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()).count(); } - double getTscGhz() const { return 1.0 / ns_per_tsc_; } + double getTscGhz() const + { + return 1.0 / ns_per_tsc_; + } // Linux kernel sync time by finding the first trial with tsc diff < 50000 // We try several times and return the one with the mininum tsc diff. // Note that MSVC has a 100ns resolution clock, so we need to combine those ns with the same // value, and drop the first and the last value as they may not scan a full 100ns range - static void syncTime(int64_t& tsc_out, int64_t& ns_out) { + static void syncTime(int64_t& tsc_out, int64_t& ns_out) + { #ifdef _MSC_VER const int N = 15; #else @@ -321,15 +369,18 @@ class fmtlogT int64_t ns[N + 1]; tsc[0] = rdtsc(); - for (int i = 1; i <= N; i++) { + for (int i = 1; i <= N; i++) + { ns[i] = rdsysns(); tsc[i] = rdtsc(); } #ifdef _MSC_VER int j = 1; - for (int i = 2; i <= N; i++) { - if (ns[i] == ns[i - 1]) continue; + for (int i = 2; i <= N; i++) + { + if (ns[i] == ns[i - 1]) + continue; tsc[j - 1] = tsc[i - 1]; ns[j++] = ns[i]; } @@ -339,14 +390,17 @@ class fmtlogT #endif int best = 1; - for (int i = 2; i < j; i++) { - if (tsc[i] - tsc[i - 1] < tsc[best] - tsc[best - 1]) best = i; + for (int i = 2; i < j; i++) + { + if (tsc[i] - tsc[i - 1] < tsc[best] - tsc[best - 1]) + best = i; } tsc_out = (tsc[best] + tsc[best - 1]) >> 1; ns_out = ns[best]; } - void saveParam(int64_t base_tsc, int64_t base_ns, int64_t sys_ns, double new_ns_per_tsc) { + void saveParam(int64_t base_tsc, int64_t base_ns, int64_t sys_ns, double new_ns_per_tsc) + { base_ns_err_ = base_ns - sys_ns; next_calibrate_tsc_ = base_tsc + (int64_t)((calibate_interval_ns_ - 1000) / new_ns_per_tsc); uint32_t seq = param_seq_.load(std::memory_order_relaxed); @@ -368,18 +422,23 @@ class fmtlogT int64_t next_calibrate_tsc_; }; - void init() { + void init() + { tscns.init(); currentLogLevel = INF; } using Context = fmt::format_context; using MemoryBuffer = fmt::basic_memory_buffer; - typedef const char* (*FormatToFn)(fmt::string_view format, const char* data, MemoryBuffer& out, - int& argIdx, std::vector>& args); + typedef const char* (*FormatToFn)( + fmt::string_view format, + const char* data, + MemoryBuffer& out, + int& argIdx, + std::vector>& args); - static void registerLogInfo(uint32_t& logId, FormatToFn fn, const char* location, LogLevel level, - std::string fmtString) noexcept; + static void + registerLogInfo(uint32_t& logId, FormatToFn fn, const char* location, LogLevel level, std::string fmtString) noexcept; static void vformat_to(MemoryBuffer& out, fmt::string_view fmt, fmt::format_args args); @@ -395,100 +454,122 @@ class fmtlogT static FAST_THREAD_LOCAL ThreadBuffer* threadBuffer; template - static inline constexpr bool isNamedArg() { + static inline constexpr bool isNamedArg() + { return fmt::detail::is_named_arg>::value; } template struct unNamedType - { using type = Arg; }; + { + using type = Arg; + }; template struct unNamedType> - { using type = Arg; }; + { + using type = Arg; + }; #if FMT_USE_NONTYPE_TEMPLATE_ARGS template Str> struct unNamedType> - { using type = Arg; }; + { + using type = Arg; + }; #endif template - static inline constexpr bool isCstring() { - return fmt::detail::mapped_type_constant::value == - fmt::detail::type::cstring_type; + static inline constexpr bool isCstring() + { + return fmt::detail::mapped_type_constant::value == fmt::detail::type::cstring_type; } template - static inline constexpr bool isString() { + static inline constexpr bool isString() + { return fmt::detail::mapped_type_constant::value == fmt::detail::type::string_type; } template - static inline constexpr bool needCallDtor() { + static inline constexpr bool needCallDtor() + { using ArgType = fmt::remove_cvref_t; - if constexpr (isNamedArg()) { + if constexpr (isNamedArg()) + { return needCallDtor::type>(); } - if constexpr (isString()) return false; + if constexpr (isString()) + return false; return !std::is_trivially_destructible::value; } template - static inline constexpr size_t getArgSizes(size_t* cstringSize) { + static inline constexpr size_t getArgSizes(size_t* cstringSize) + { return 0; } template - static inline constexpr size_t getArgSizes(size_t* cstringSize, const Arg& arg, - const Args&... args) { - if constexpr (isNamedArg()) { + static inline constexpr size_t getArgSizes(size_t* cstringSize, const Arg& arg, const Args&... args) + { + if constexpr (isNamedArg()) + { return getArgSizes(cstringSize, arg.value, args...); } - else if constexpr (isCstring()) { + else if constexpr (isCstring()) + { size_t len = strlen(arg) + 1; cstringSize[CstringIdx] = len; return len + getArgSizes(cstringSize, args...); } - else if constexpr (isString()) { + else if constexpr (isString()) + { size_t len = arg.size() + 1; return len + getArgSizes(cstringSize, args...); } - else { + else + { return sizeof(Arg) + getArgSizes(cstringSize, args...); } } template - static inline constexpr char* encodeArgs(size_t* cstringSize, char* out) { + static inline constexpr char* encodeArgs(size_t* cstringSize, char* out) + { return out; } template - static inline constexpr char* encodeArgs(size_t* cstringSize, char* out, Arg&& arg, - Args&&... args) { - if constexpr (isNamedArg()) { + static inline constexpr char* encodeArgs(size_t* cstringSize, char* out, Arg&& arg, Args&&... args) + { + if constexpr (isNamedArg()) + { return encodeArgs(cstringSize, out, arg.value, std::forward(args)...); } - else if constexpr (isCstring()) { + else if constexpr (isCstring()) + { memcpy(out, arg, cstringSize[CstringIdx]); - return encodeArgs(cstringSize, out + cstringSize[CstringIdx], - std::forward(args)...); + return encodeArgs(cstringSize, out + cstringSize[CstringIdx], std::forward(args)...); } - else if constexpr (isString()) { + else if constexpr (isString()) + { size_t len = arg.size(); memcpy(out, arg.data(), len); out[len] = 0; return encodeArgs(cstringSize, out + len + 1, std::forward(args)...); } - else { + else + { // If Arg has alignment >= 16, gcc could emit aligned move instructions(e.g. movdqa) for // placement new even if the *out* is misaligned, which would cause segfault. So we use memcpy // when possible - if constexpr (std::is_trivially_copyable_v>) { + if constexpr (std::is_trivially_copyable_v>) + { memcpy(out, &arg, sizeof(Arg)); } - else { + else + { new (out) fmt::remove_cvref_t(std::forward(arg)); } return encodeArgs(cstringSize, out + sizeof(Arg), std::forward(args)...); @@ -496,111 +577,137 @@ class fmtlogT } template - static inline constexpr void storeNamedArgs(fmt::detail::named_arg_info* named_args_store) { + static inline constexpr void storeNamedArgs(fmt::detail::named_arg_info* named_args_store) + { } template - static inline constexpr void storeNamedArgs(fmt::detail::named_arg_info* named_args_store, - const Arg& arg, const Args&... args) { - if constexpr (isNamedArg()) { - named_args_store[NamedIdx] = {arg.name, Idx}; + static inline constexpr void + storeNamedArgs(fmt::detail::named_arg_info* named_args_store, const Arg& arg, const Args&... args) + { + if constexpr (isNamedArg()) + { + named_args_store[NamedIdx] = { arg.name, Idx }; storeNamedArgs(named_args_store, args...); } - else { + else + { storeNamedArgs(named_args_store, args...); } } template - static inline const char* decodeArgs(const char* in, fmt::basic_format_arg* args, - const char** destruct_args) { + static inline const char* decodeArgs(const char* in, fmt::basic_format_arg* args, const char** destruct_args) + { return in; } template - static inline const char* decodeArgs(const char* in, fmt::basic_format_arg* args, - const char** destruct_args) { + static inline const char* decodeArgs(const char* in, fmt::basic_format_arg* args, const char** destruct_args) + { using namespace fmtlogdetail; using ArgType = fmt::remove_cvref_t; - if constexpr (isNamedArg()) { - return decodeArgs::type, Args...>( - in, args, destruct_args); + if constexpr (isNamedArg()) + { + return decodeArgs::type, Args...>(in, args, destruct_args); } - else if constexpr (isCstring() || isString()) { + else if constexpr (isCstring() || isString()) + { size_t size = strlen(in); fmt::string_view v(in, size); - if constexpr (ValueOnly) { + if constexpr (ValueOnly) + { fmt::detail::value& value_ = *(fmt::detail::value*)(args + Idx); value_ = fmt::detail::arg_mapper().map(v); } - else { + else + { args[Idx] = fmt::detail::make_arg(v); } - return decodeArgs(in + size + 1, args, - destruct_args); + return decodeArgs(in + size + 1, args, destruct_args); } - else { - if constexpr (ValueOnly) { + else + { + if constexpr (ValueOnly) + { fmt::detail::value& value_ = *(fmt::detail::value*)(args + Idx); - if constexpr (UnrefPtr::value) { + if constexpr (UnrefPtr::value) + { value_ = fmt::detail::arg_mapper().map(**(ArgType*)in); } - else { + else + { value_ = fmt::detail::arg_mapper().map(*(ArgType*)in); } } - else { - if constexpr (UnrefPtr::value) { + else + { + if constexpr (UnrefPtr::value) + { args[Idx] = fmt::detail::make_arg(**(ArgType*)in); } - else { + else + { args[Idx] = fmt::detail::make_arg(*(ArgType*)in); } } - if constexpr (needCallDtor()) { + if constexpr (needCallDtor()) + { destruct_args[DestructIdx] = in; - return decodeArgs(in + sizeof(ArgType), args, - destruct_args); + return decodeArgs(in + sizeof(ArgType), args, destruct_args); } - else { - return decodeArgs(in + sizeof(ArgType), args, - destruct_args); + else + { + return decodeArgs(in + sizeof(ArgType), args, destruct_args); } } } template - static inline void destructArgs(const char** destruct_args) {} + static inline void destructArgs(const char** destruct_args) + { + } template - static inline void destructArgs(const char** destruct_args) { + static inline void destructArgs(const char** destruct_args) + { using ArgType = fmt::remove_cvref_t; - if constexpr (isNamedArg()) { + if constexpr (isNamedArg()) + { destructArgs::type, Args...>(destruct_args); } - else if constexpr (needCallDtor()) { + else if constexpr (needCallDtor()) + { ((ArgType*)destruct_args[DestructIdx])->~ArgType(); destructArgs(destruct_args); } - else { + else + { destructArgs(destruct_args); } } template - static const char* formatTo(fmt::string_view format, const char* data, MemoryBuffer& out, - int& argIdx, std::vector>& args) { + static const char* formatTo( + fmt::string_view format, + const char* data, + MemoryBuffer& out, + int& argIdx, + std::vector>& args) + { constexpr size_t num_args = sizeof...(Args); constexpr size_t num_dtors = fmt::detail::count()...>(); const char* dtor_args[std::max(num_dtors, (size_t)1)]; const char* ret; - if (argIdx < 0) { + if (argIdx < 0) + { argIdx = (int)args.size(); args.resize(argIdx + num_args); ret = decodeArgs(data, args.data() + argIdx, dtor_args); } - else { + else + { ret = decodeArgs(data, args.data() + argIdx, dtor_args); } vformat_to(out, format, fmt::basic_format_args(args.data() + argIdx, num_args)); @@ -610,10 +717,11 @@ class fmtlogT } template - static std::string unNameFormat(std::string in, uint32_t* reorderIdx, - const Args&... args) { + static std::string unNameFormat(std::string in, uint32_t* reorderIdx, const Args&... args) + { constexpr size_t num_named_args = fmt::detail::count()...>(); - if constexpr (num_named_args == 0) { + if constexpr (num_named_args == 0) + { return in; } const char* begin = in.data(); @@ -625,70 +733,89 @@ class fmtlogT char* out = (char*)unnamed_str.get(); uint8_t arg_idx = 0; - while (true) { + while (true) + { auto c = *p++; - if (!c) { + if (!c) + { size_t copy_size = p - begin - 1; memcpy(out, begin, copy_size); out += copy_size; break; } - if (c != '{') continue; + if (c != '{') + continue; size_t copy_size = p - begin; memcpy(out, begin, copy_size); out += copy_size; begin = p; c = *p++; - if (!c) fmt::throw_format_error("invalid format string"); - if (fmt::detail::is_name_start(c)) { - while ((fmt::detail::is_name_start(c = *p) || ('0' <= c && c <= '9'))) { + if (!c) + fmt::throw_format_error("invalid format string"); + if (fmt::detail::is_name_start(c)) + { + while ((fmt::detail::is_name_start(c = *p) || ('0' <= c && c <= '9'))) + { ++p; } fmt::string_view name(begin, p - begin); int id = -1; - for (size_t i = 0; i < num_named_args; ++i) { - if (named_args[i].name == name) { + for (size_t i = 0; i < num_named_args; ++i) + { + if (named_args[i].name == name) + { id = named_args[i].id; break; } } - if (id < 0) fmt::throw_format_error("invalid format string"); - if constexpr (Reorder) { + if (id < 0) + fmt::throw_format_error("invalid format string"); + if constexpr (Reorder) + { reorderIdx[id] = arg_idx++; } - else { + else + { out = fmt::format_to(out, "{}", id); } } - else { + else + { *out++ = c; } begin = p; } std::string result; result.reserve(size_unnamed_str); - std::string_view ptr_span(unnamed_str.get(),size_unnamed_str); - std::copy_n(ptr_span.begin(),out - unnamed_str.get(),std::back_inserter(result)); + std::string_view ptr_span(unnamed_str.get(), size_unnamed_str); + std::copy_n(ptr_span.begin(), out - unnamed_str.get(), std::back_inserter(result)); return result; } -public: + public: template inline void log( - uint32_t& logId, int64_t tsc, const char* location, LogLevel level, - fmt::format_string>::type...> format, - Args&&... args) noexcept { - if (!logId) { + uint32_t& logId, + int64_t tsc, + const char* location, + LogLevel level, + fmt::format_string>::type...> format, + Args&&... args) noexcept + { + if (!logId) + { fmt::string_view format_str_v = format.get(); - auto unnamed_format = unNameFormat(std::string{format_str_v.begin(),format_str_v.end()}, nullptr, args...); + auto unnamed_format = unNameFormat(std::string{ format_str_v.begin(), format_str_v.end() }, nullptr, args...); registerLogInfo(logId, formatTo, location, level, unnamed_format); } constexpr size_t num_cstring = fmt::detail::count()...>(); size_t cstringSizes[std::max(num_cstring, (size_t)1)]; uint32_t alloc_size = 8 + (uint32_t)getArgSizes<0>(cstringSizes, args...); bool q_full_cb = true; - do { - if (auto header = allocMsg(alloc_size, q_full_cb)) { + do + { + if (auto header = allocMsg(alloc_size, q_full_cb)) + { header->logId = logId; char* out = (char*)(header + 1); *(int64_t*)out = tsc; @@ -702,15 +829,17 @@ class fmtlogT } template - inline void logOnce(const char* location, LogLevel level, fmt::format_string format, - Args&&... args) { + inline void logOnce(const char* location, LogLevel level, fmt::format_string format, Args&&... args) + { fmt::string_view sv(format); auto&& fmt_args = fmt::make_format_args(args...); uint32_t fmt_size = formatted_size(sv, fmt_args); uint32_t alloc_size = 8 + 8 + fmt_size; bool q_full_cb = true; - do { - if (auto header = allocMsg(alloc_size, q_full_cb)) { + do + { + if (auto header = allocMsg(alloc_size, q_full_cb)) + { header->logId = (uint32_t)level; char* out = (char*)(header + 1); *(int64_t*)out = tscns.rdtsc(); @@ -733,23 +862,28 @@ FAST_THREAD_LOCAL typename fmtlogT<_>::ThreadBuffer* fmtlogT<_>::threadBuffer; template struct fmtlogWrapper -{ static fmtlog impl; }; +{ + static fmtlog impl; +}; template fmtlog fmtlogWrapper<_>::impl; template -inline void fmtlogT<_>::setLogLevel(LogLevel logLevel) noexcept { +inline void fmtlogT<_>::setLogLevel(LogLevel logLevel) noexcept +{ fmtlogWrapper<>::impl.currentLogLevel = logLevel; } template -inline typename fmtlogT<_>::LogLevel fmtlogT<_>::getLogLevel() noexcept { +inline typename fmtlogT<_>::LogLevel fmtlogT<_>::getLogLevel() noexcept +{ return fmtlogWrapper<>::impl.currentLogLevel; } template -inline bool fmtlogT<_>::checkLogLevel(LogLevel logLevel) noexcept { +inline bool fmtlogT<_>::checkLogLevel(LogLevel logLevel) noexcept +{ #ifdef FMTLOG_NO_CHECK_LEVEL return true; #else @@ -757,73 +891,79 @@ inline bool fmtlogT<_>::checkLogLevel(LogLevel logLevel) noexcept { #endif } -#define __FMTLOG_S1(x) #x -#define __FMTLOG_S2(x) __FMTLOG_S1(x) +#define __FMTLOG_S1(x) #x +#define __FMTLOG_S2(x) __FMTLOG_S1(x) #define __FMTLOG_LOCATION __FILE__ ":" __FMTLOG_S2(__LINE__) -#define FMTLOG(level, format, ...) \ - do { \ - static uint32_t logId = 0; \ - if (!fmtlog::checkLogLevel(level)) break; \ - fmtlogWrapper<>::impl.log(logId, fmtlogWrapper<>::impl.tscns.rdtsc(), __FMTLOG_LOCATION, \ - level, format, ##__VA_ARGS__); \ +#define FMTLOG(level, format, ...) \ + do \ + { \ + static uint32_t logId = 0; \ + if (!fmtlog::checkLogLevel(level)) \ + break; \ + fmtlogWrapper<>::impl.log(logId, fmtlogWrapper<>::impl.tscns.rdtsc(), __FMTLOG_LOCATION, level, format, ##__VA_ARGS__); \ } while (0) -#define FMTLOG_LIMIT(min_interval, level, format, ...) \ - do { \ - static uint32_t logId = 0; \ - static int64_t limitNs = 0; \ - if (!fmtlog::checkLogLevel(level)) break; \ - int64_t tsc = fmtlogWrapper<>::impl.tscns.rdtsc(); \ - int64_t ns = fmtlogWrapper<>::impl.tscns.tsc2ns(tsc); \ - if (ns < limitNs) break; \ - limitNs = ns + min_interval; \ - fmtlogWrapper<>::impl.log(logId, tsc, __FMTLOG_LOCATION, level, format, ##__VA_ARGS__); \ +#define FMTLOG_LIMIT(min_interval, level, format, ...) \ + do \ + { \ + static uint32_t logId = 0; \ + static int64_t limitNs = 0; \ + if (!fmtlog::checkLogLevel(level)) \ + break; \ + int64_t tsc = fmtlogWrapper<>::impl.tscns.rdtsc(); \ + int64_t ns = fmtlogWrapper<>::impl.tscns.tsc2ns(tsc); \ + if (ns < limitNs) \ + break; \ + limitNs = ns + min_interval; \ + fmtlogWrapper<>::impl.log(logId, tsc, __FMTLOG_LOCATION, level, format, ##__VA_ARGS__); \ } while (0) -#define FMTLOG_ONCE(level, format, ...) \ - do { \ - if (!fmtlog::checkLogLevel(level)) break; \ - fmtlogWrapper<>::impl.logOnce(__FMTLOG_LOCATION, level, format, ##__VA_ARGS__); \ +#define FMTLOG_ONCE(level, format, ...) \ + do \ + { \ + if (!fmtlog::checkLogLevel(level)) \ + break; \ + fmtlogWrapper<>::impl.logOnce(__FMTLOG_LOCATION, level, format, ##__VA_ARGS__); \ } while (0) #if FMTLOG_ACTIVE_LEVEL <= FMTLOG_LEVEL_DBG -#define logd(format, ...) FMTLOG(fmtlog::DBG, format, ##__VA_ARGS__) -#define logdo(format, ...) FMTLOG_ONCE(fmtlog::DBG, format, ##__VA_ARGS__) +#define logd(format, ...) FMTLOG(fmtlog::DBG, format, ##__VA_ARGS__) +#define logdo(format, ...) FMTLOG_ONCE(fmtlog::DBG, format, ##__VA_ARGS__) #define logdl(min_interval, format, ...) FMTLOG_LIMIT(min_interval, fmtlog::DBG, format, ##__VA_ARGS__) #else -#define logd(format, ...) (void)0 -#define logdo(format, ...) (void)0 +#define logd(format, ...) (void)0 +#define logdo(format, ...) (void)0 #define logdl(min_interval, format, ...) (void)0 #endif #if FMTLOG_ACTIVE_LEVEL <= FMTLOG_LEVEL_INF -#define logi(format, ...) FMTLOG(fmtlog::INF, format, ##__VA_ARGS__) -#define logio(format, ...) FMTLOG_ONCE(fmtlog::INF, format, ##__VA_ARGS__) +#define logi(format, ...) FMTLOG(fmtlog::INF, format, ##__VA_ARGS__) +#define logio(format, ...) FMTLOG_ONCE(fmtlog::INF, format, ##__VA_ARGS__) #define logil(min_interval, format, ...) FMTLOG_LIMIT(min_interval, fmtlog::INF, format, ##__VA_ARGS__) #else -#define logi(format, ...) (void)0 -#define logio(format, ...) (void)0 +#define logi(format, ...) (void)0 +#define logio(format, ...) (void)0 #define logil(min_interval, format, ...) (void)0 #endif #if FMTLOG_ACTIVE_LEVEL <= FMTLOG_LEVEL_WRN -#define logw(format, ...) FMTLOG(fmtlog::WRN, format, ##__VA_ARGS__) -#define logwo(format, ...) FMTLOG_ONCE(fmtlog::WRN, format, ##__VA_ARGS__) +#define logw(format, ...) FMTLOG(fmtlog::WRN, format, ##__VA_ARGS__) +#define logwo(format, ...) FMTLOG_ONCE(fmtlog::WRN, format, ##__VA_ARGS__) #define logwl(min_interval, format, ...) FMTLOG_LIMIT(min_interval, fmtlog::WRN, format, ##__VA_ARGS__) #else -#define logw(format, ...) (void)0 -#define logwo(format, ...) (void)0 +#define logw(format, ...) (void)0 +#define logwo(format, ...) (void)0 #define logwl(min_interval, format, ...) (void)0 #endif #if FMTLOG_ACTIVE_LEVEL <= FMTLOG_LEVEL_ERR -#define loge(format, ...) FMTLOG(fmtlog::ERR, format, ##__VA_ARGS__) -#define logeo(format, ...) FMTLOG_ONCE(fmtlog::ERR, format, ##__VA_ARGS__) +#define loge(format, ...) FMTLOG(fmtlog::ERR, format, ##__VA_ARGS__) +#define logeo(format, ...) FMTLOG_ONCE(fmtlog::ERR, format, ##__VA_ARGS__) #define logel(min_interval, format, ...) FMTLOG_LIMIT(min_interval, fmtlog::ERR, format, ##__VA_ARGS__) #else -#define loge(format, ...) (void)0 -#define logeo(format, ...) (void)0 +#define loge(format, ...) (void)0 +#define logeo(format, ...) (void)0 #define logel(min_interval, format, ...) (void)0 #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 207be23..f0121f7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,94 +1,74 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14...3.22) -# -# Project details -# +project(fmtlogTests LANGUAGES CXX) -project( - ${CMAKE_PROJECT_NAME}Tests - LANGUAGES CXX -) +# ---- Options ---- -include(../cmake/Utils.cmake) +option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) +option(TEST_INSTALLED_VERSION "Test the version found by find_package" OFF) -set(${CMAKE_PROJECT_NAME}_USE_GTEST OFF) -set(${CMAKE_PROJECT_NAME}_USE_CATCH2 OFF) +# --- Import tools ---- -verbose_message("Adding tests under ${CMAKE_PROJECT_NAME}Tests...") +include(../cmake/tools.cmake) -foreach(file ${test_sources}) - string(REGEX REPLACE "(.*\/)([a-zA-Z0-9_ ]+)(\.c[c,pp])" "\\2" test_name ${file}) - message(DEBUG "test names: " ${test_name}_Tests) - add_executable(${test_name}_Tests ${file}) +# ---- Dependencies ---- - # - # Set the compiler standard - # +include(../cmake/getCPM.cmake) +cpmaddpackage("gh:TheLartians/Format.cmake@1.7.3") - target_compile_features(${test_name}_Tests PUBLIC cxx_std_17) +if(TEST_INSTALLED_VERSION) + find_package(fmtlog REQUIRED) +else() + cpmaddpackage(NAME fmtlog SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) +endif() - # - # Setup code coverage if enabled - # +# ---- Create binary ---- - if (${CMAKE_PROJECT_NAME}_ENABLE_CODE_COVERAGE) - target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC -O0 -g -fprofile-arcs -ftest-coverage) - target_link_options(${CMAKE_PROJECT_NAME} PUBLIC -fprofile-arcs -ftest-coverage) - verbose_message("Code coverage is enabled and provided with GCC.") - endif() - - # - # Load the desired unit testing framework - # - # Currently supported: GoogleTest (and GoogleMock), Catch2. - - if(${CMAKE_PROJECT_NAME}_BUILD_EXECUTABLE) - set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}_LIB) - else() - set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}) - endif() +file( + GLOB_RECURSE sources CONFIGURE_DEPENDS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/src/ + ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) - if(${CMAKE_PROJECT_NAME}_USE_GTEST) - find_package(GTest REQUIRED) - - if(${CMAKE_PROJECT_NAME}_USE_GOOGLE_MOCK) - set(GOOGLE_MOCK_LIBRARIES GTest::gmock GTest::gmock_main) - endif() - - target_link_libraries( - ${test_name}_Tests - PUBLIC - GTest::GTest - GTest::Main - ${GOOGLE_MOCK_LIBRARIES} - ${${CMAKE_PROJECT_NAME}_TEST_LIB} - ) - elseif(${CMAKE_PROJECT_NAME}_USE_CATCH2) - find_package(Catch2 REQUIRED) - target_link_libraries( - ${test_name}_Tests - PUBLIC - Catch2::Catch2 - ${${CMAKE_PROJECT_NAME}_TEST_LIB} - ) - else() - message(WARNING "Unknown testing library. Please setup your desired unit testing library by using `target_link_libraries`.") +foreach(source ${sources}) + string(REPLACE "." "_" name ${source}) + string(REPLACE "/" "_" name ${source}) + message(DEBUG "Adding test with name ${PROJECT_NAME}_test_${name}") + add_executable(${PROJECT_NAME}_test_${name} + ${CMAKE_CURRENT_SOURCE_DIR}/src/${source}) + target_link_libraries(${PROJECT_NAME}_test_${name} PRIVATE fmtlog::fmtlog) + target_link_libraries(${PROJECT_NAME}_test_${name} PRIVATE gtest_main) + set_target_properties(${PROJECT_NAME}_test_${name} PROPERTIES CXX_STANDARD 17) + add_test(NAME ${PROJECT_NAME}_test_${name} + COMMAND ${PROJECT_NAME}_test_${name}) +endforeach() +# enable compiler warnings +if(NOT TEST_INSTALLED_VERSION) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(fmtlog INTERFACE -Wall -Wpedantic -Wextra) + elseif(MSVC) + target_compile_options(fmtlog INTERFACE /W4 /WX) + # target_compile_definitions(${PROJECT_NAME} PUBLIC + # DOCTEST_CONFIG_USE_STD_HEADERS) endif() +endif() +# ---- Add fmtlogTests ---- - target_link_libraries(${test_name}_Tests PRIVATE fmtlog::fmtlog) +enable_testing() +# Note: doctest and similar testing frameworks can automatically configure CMake +# tests. For other testing frameworks add the tests target instead: +# add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) - # - # Add the unit tests - # +# if(NOT DEFINED doctest_ADDED OR "${doctest_ADDED}" STREQUAL "") +# include("/usr/lib64/cmake/doctest/doctest.cmake") else() +# include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake) endif() +# doctest_discover_tests(${PROJECT_NAME}) - add_test( - NAME - ${test_name} - COMMAND - ${test_name}_Tests - ) -endforeach() +# ---- code coverage ---- -verbose_message("Finished adding unit tests for ${CMAKE_PROJECT_NAME}.") +if(ENABLE_TEST_COVERAGE) + target_compile_options(fmtlog INTERFACE -O0 -g -fprofile-arcs -ftest-coverage) + target_link_options(fmtlog INTERFACE -fprofile-arcs -ftest-coverage) +endif()