├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── cmake ├── CompilerWarnings.cmake ├── Packaging.cmake ├── StandardProjectSettings.cmake ├── StaticAnalyzers.cmake └── UniversalAppleBuild.cmake ├── src ├── CMakeLists.txt ├── app │ ├── App │ │ └── Main.cpp │ └── CMakeLists.txt ├── some_library │ ├── CMakeLists.txt │ ├── SomeLibrary │ │ ├── Core │ │ │ ├── Log.cpp │ │ │ └── Log.hpp │ │ └── Debug │ │ │ └── Instrumentor.hpp │ └── Tests │ │ ├── CMakeLists.txt │ │ └── Resources.spec.cpp └── tests │ ├── CMakeLists.txt │ └── TestRunner.cpp └── vendor ├── CMakeLists.txt ├── doctest └── CMakeLists.txt ├── fmt └── CMakeLists.txt └── spdlog └── CMakeLists.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | AlignAfterOpenBracket: DontAlign 5 | AllowShortBlocksOnASingleLine: Empty 6 | AllowShortFunctionsOnASingleLine: Empty 7 | AllowShortCaseLabelsOnASingleLine: false 8 | AllowShortIfStatementsOnASingleLine: false 9 | AllowShortLambdasOnASingleLine: Empty 10 | AllowShortLoopsOnASingleLine: false 11 | AllowAllConstructorInitializersOnNextLine: false 12 | BinPackArguments: false 13 | BinPackParameters: false 14 | ColumnLimit: 100 15 | 16 | ... 17 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | *, 4 | -android-*, 5 | -abseil-*, 6 | -altera-*, 7 | -darwin-*, 8 | -fuchsia-*, 9 | -google-*, 10 | -objc-*, 11 | -zircon-*, 12 | -llvm*, 13 | -hicpp*, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 16 | -cppcoreguidelines-macro-usage, 17 | -cppcoreguidelines-pro-type-vararg, 18 | -cppcoreguidelines-avoid-magic-numbers, 19 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 20 | -readability-magic-numbers, 21 | -readability-function-cognitive-complexity, 22 | -misc-non-private-member-variables-in-classes, 23 | -clang-analyzer-optin.cplusplus.UninitializedObject, 24 | -misc-static-assert, 25 | -modernize-use-trailing-return-type, 26 | -modernize-use-nullptr, 27 | -bugprone-easily-swappable-parameters, 28 | -bugprone-exception-escape, 29 | -cert-env33-c, 30 | -cert-err58-cpp 31 | 32 | # -modernize-use-nullptr is deactivated for x86 33 | # See: https://github.com/llvm/llvm-project/issues/53778 34 | 35 | WarningsAsErrors: '*' 36 | HeaderFilterRegex: '' 37 | FormatStyle: none 38 | 39 | CheckOptions: 40 | - { key: readability-identifier-naming.NamespaceCase, value: CamelCase } 41 | - { key: readability-identifier-naming.ClassCase, value: CamelCase } 42 | - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } 43 | - { key: readability-identifier-naming.StructCase, value: CamelCase } 44 | - { key: readability-identifier-naming.ClassMethodCase, value: lower_case } 45 | - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } 46 | - { key: readability-identifier-naming.FunctionCase, value: lower_case } 47 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 48 | - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } 49 | - { key: readability-identifier-length.MinimumVariableNameLength, value: 2 } 50 | - { key: readability-identifier-length.MinimumParameterNameLength, value: 2 } 51 | - { key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors, value: '1' } 52 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: MartinHelmut 2 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Install GitHub CLI 20 | run: | 21 | brew update 22 | brew install llvm 23 | ln -s "$(brew --prefix llvm)/bin/clang-format" "/usr/local/bin/clang-format" 24 | ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" 25 | 26 | - name: Configure CMake 27 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 28 | 29 | - name: Build 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | 32 | - name: Test 33 | working-directory: ${{github.workspace}}/build 34 | run: ctest -C ${{env.BUILD_TYPE}} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Application build output 2 | build/ 3 | distribution/ 4 | 5 | # Created by CTest when executing tests. 6 | Testing/ 7 | 8 | # Generated by the profiler on debug. 9 | profile.json 10 | *-profile.json 11 | 12 | # Created by running the application. 13 | *.log 14 | 15 | # User defined CMake preset file. 16 | CMakeUserPresets.json 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | include(cmake/UniversalAppleBuild.cmake) 4 | 5 | project( 6 | BasicProjectSetup 7 | DESCRIPTION "Base project setup." 8 | LANGUAGES CXX) 9 | 10 | set(CMAKE_CXX_STANDARD 20) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | 13 | include(cmake/StandardProjectSettings.cmake) 14 | 15 | # Link this "library" to use the warnings specified in CompilerWarnings.cmake 16 | add_library(project_warnings INTERFACE) 17 | include(cmake/CompilerWarnings.cmake) 18 | set_project_warnings(project_warnings) 19 | 20 | include(cmake/Packaging.cmake) 21 | 22 | enable_testing() 23 | 24 | add_subdirectory(vendor) 25 | add_subdirectory(src) 26 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | "configurePresets": [ 4 | { 5 | "name": "debug", 6 | "displayName": "Debug", 7 | "generator": "Ninja", 8 | "binaryDir": "build/debug", 9 | "cacheVariables": { 10 | "CMAKE_BUILD_TYPE": "Debug" 11 | } 12 | }, 13 | { 14 | "name": "release", 15 | "displayName": "Release", 16 | "generator": "Ninja", 17 | "binaryDir": "build/release", 18 | "cacheVariables": { 19 | "CMAKE_BUILD_TYPE": "Release" 20 | } 21 | }, 22 | { 23 | "name": "xcode-debug", 24 | "displayName": "Debug (Xcode)", 25 | "generator": "Xcode", 26 | "binaryDir": "build/xcode-debug", 27 | "cacheVariables": { 28 | "CMAKE_BUILD_TYPE": "Debug" 29 | }, 30 | "condition": { 31 | "type": "equals", 32 | "lhs": "${hostSystemName}", 33 | "rhs": "Darwin" 34 | } 35 | }, 36 | { 37 | "name": "xcode-release", 38 | "displayName": "Release (Xcode)", 39 | "generator": "Xcode", 40 | "binaryDir": "build/xcode-release", 41 | "cacheVariables": { 42 | "CMAKE_BUILD_TYPE": "Release" 43 | }, 44 | "condition": { 45 | "type": "equals", 46 | "lhs": "${hostSystemName}", 47 | "rhs": "Darwin" 48 | } 49 | } 50 | ], 51 | "buildPresets": [ 52 | { 53 | "name": "debug", 54 | "displayName": "Build Debug", 55 | "configurePreset": "debug", 56 | "configuration": "Debug" 57 | }, 58 | { 59 | "name": "release", 60 | "displayName": "Build Release", 61 | "configurePreset": "release", 62 | "configuration": "Release", 63 | "targets": [ 64 | "App" 65 | ] 66 | }, 67 | { 68 | "name": "xcode-debug", 69 | "displayName": "Build Debug (Xcode)", 70 | "configurePreset": "xcode-debug", 71 | "configuration": "Debug", 72 | "condition": { 73 | "type": "equals", 74 | "lhs": "${hostSystemName}", 75 | "rhs": "Darwin" 76 | } 77 | }, 78 | { 79 | "name": "xcode-release", 80 | "displayName": "Build Release (Xcode)", 81 | "configurePreset": "xcode-release", 82 | "configuration": "Release", 83 | "targets": [ 84 | "App" 85 | ], 86 | "condition": { 87 | "type": "equals", 88 | "lhs": "${hostSystemName}", 89 | "rhs": "Darwin" 90 | } 91 | } 92 | ], 93 | "packagePresets": [ 94 | { 95 | "name": "release", 96 | "displayName": "Distribute Release", 97 | "configurePreset": "release", 98 | "configurations": [ 99 | "Release" 100 | ] 101 | }, 102 | { 103 | "name": "xcode-release", 104 | "displayName": "Distribute Release (Xcode)", 105 | "configurePreset": "xcode-release", 106 | "configurations": [ 107 | "Release" 108 | ], 109 | "condition": { 110 | "type": "equals", 111 | "lhs": "${hostSystemName}", 112 | "rhs": "Darwin" 113 | } 114 | } 115 | ], 116 | "testPresets": [ 117 | { 118 | "name": "all", 119 | "displayName": "Test All", 120 | "configurePreset": "debug" 121 | } 122 | ], 123 | "workflowPresets": [ 124 | { 125 | "name": "dist", 126 | "displayName": "Distribution Workflow", 127 | "steps": [ 128 | { 129 | "type": "configure", 130 | "name": "release" 131 | }, 132 | { 133 | "type": "build", 134 | "name": "release" 135 | }, 136 | { 137 | "type": "package", 138 | "name": "release" 139 | } 140 | ] 141 | }, 142 | { 143 | "name": "xcode-dist", 144 | "displayName": "Distribution Workflow (Xcode)", 145 | "steps": [ 146 | { 147 | "type": "configure", 148 | "name": "xcode-release" 149 | }, 150 | { 151 | "type": "build", 152 | "name": "xcode-release" 153 | }, 154 | { 155 | "type": "package", 156 | "name": "xcode-release" 157 | } 158 | ] 159 | } 160 | ] 161 | } 162 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | 132 | [Mozilla CoC]: https://github.com/mozilla/diversity 133 | 134 | [FAQ]: https://www.contributor-covenant.org/faq 135 | 136 | [translations]: https://www.contributor-covenant.org/translations 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Base project setup 2 | 3 | C++ project template. 4 | 5 | ## Setup 6 | 7 | The project uses [CMake](https://cmake.org) and [Ninja](https://ninja-build.org). 8 | 9 | Thanks to Cmake presets, a full distribution build can be created via: 10 | 11 | ```shell 12 | cmake --workflow --preset dist 13 | ``` 14 | 15 | Also, running tests with CPack: 16 | 17 | ```shell 18 | ctest --preset all 19 | ``` 20 | 21 | ## Disclaimer 22 | 23 | An in-depth guide for the project template can be found at 24 | the [blog post I wrote](https://martin-fieber.de/blog/basic-cpp-setup-with-dependency-management) at my website. 25 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 2 | 3 | function(set_project_warnings project_name) 4 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 5 | message(STATUS "Treat compiler warnings as errors") 6 | 7 | set(MSVC_WARNINGS 8 | /W4 # Baseline reasonable warnings 9 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss 10 | # of data 11 | /w14254 # 'operator': conversion from 'type1:field_bits' to 12 | # 'type2:field_bits', possible loss of data 13 | /w14263 # 'function': member function does not override any base class 14 | # virtual member function 15 | /w14265 # 'classname': class has virtual functions, but destructor is not 16 | # virtual instances of this class may not be destructed correctly 17 | /w14287 # 'operator': unsigned/negative constant mismatch 18 | /we4289 # nonstandard extension used: 'variable': loop control variable 19 | # declared in the for-loop is used outside the for-loop scope 20 | /w14296 # 'operator': expression is always 'boolean_value' 21 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 22 | /w14545 # expression before comma evaluates to a function which is missing 23 | # an argument list 24 | /w14546 # function call before comma missing argument list 25 | /w14547 # 'operator': operator before comma has no effect; expected 26 | # operator with side-effect 27 | /w14549 # 'operator': operator before comma has no effect; did you intend 28 | # 'operator'? 29 | /w14555 # expression has no effect; expected expression with side- effect 30 | /w14619 # pragma warning: there is no warning number 'number' 31 | /w14640 # Enable warning on thread un-safe static member initialization 32 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may 33 | # cause unexpected runtime behavior. 34 | /w14905 # wide string literal cast to 'LPSTR' 35 | /w14906 # string literal cast to 'LPWSTR' 36 | /w14928 # illegal copy-initialization; more than one user-defined 37 | # conversion has been implicitly applied 38 | /permissive- # standards conformance mode for MSVC compiler. 39 | ) 40 | 41 | set(CLANG_WARNINGS 42 | -Wall 43 | -Wextra # reasonable and standard 44 | # -Wshadow # warn the user if a variable declaration shadows one from a 45 | # parent context 46 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a 47 | # non-virtual destructor. This helps catch hard to 48 | # track down memory errors 49 | -Wold-style-cast # warn for c-style casts 50 | -Wcast-align # warn for potential performance problem casts 51 | -Wunused # warn on anything being unused 52 | -Woverloaded-virtual # warn if you overload (not override) a virtual 53 | # function 54 | -Wpedantic # warn if non-standard C++ is used 55 | -Wconversion # warn on type conversions that may lose data 56 | -Wsign-conversion # warn on sign conversions 57 | -Wnull-dereference # warn if a null dereference is detected 58 | -Wdouble-promotion # warn if float is implicit promoted to double 59 | -Wformat=2 # warn on security issues around functions that format output 60 | # (ie printf) 61 | ) 62 | 63 | if (WARNINGS_AS_ERRORS) 64 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 65 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 66 | endif () 67 | 68 | set(GCC_WARNINGS 69 | ${CLANG_WARNINGS} 70 | -Wmisleading-indentation # warn if indentation implies blocks where blocks 71 | -Wduplicated-branches # warn if if / else branches have duplicated code 72 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 73 | -Wlogical-op # warn about logical operations being used where bitwise were 74 | -Wuseless-cast # warn if you perform a cast to the same type 75 | ) 76 | 77 | if (MSVC) 78 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 79 | elseif (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 80 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 81 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 82 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 83 | else () 84 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 85 | endif () 86 | 87 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 88 | 89 | endfunction() 90 | -------------------------------------------------------------------------------- /cmake/Packaging.cmake: -------------------------------------------------------------------------------- 1 | # Base package settings 2 | set(CPACK_PACKAGE_VENDOR ${PROJECT_COMPANY_NAME}) 3 | set(CPACK_PACKAGE_DIRECTORY distribution) 4 | set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) 5 | set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}") 6 | set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR}) 7 | set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR}) 8 | set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH}) 9 | set(CPACK_VERBATIM_VARIABLES YES) 10 | 11 | set(CPACK_GENERATOR TGZ) 12 | 13 | include(CPack) 14 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'Debug' as none was specified.") 4 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) 5 | 6 | # Set the possible values of build type for cmake-gui, ccmake 7 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") 8 | endif () 9 | 10 | find_program(CCACHE ccache) 11 | if (CCACHE) 12 | message(STATUS "Using ccache") 13 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 14 | else () 15 | message(STATUS "Ccache not found") 16 | endif () 17 | 18 | # Generate compile_commands.json to make it easier to work with clang based tools 19 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 20 | 21 | option(DEACTIVATE_LOGGING "Disable logging" OFF) 22 | if (DEACTIVATE_LOGGING) 23 | add_compile_definitions(APP_DEACTIVATE_LOGGING) 24 | endif () 25 | 26 | option(DEBUG "Enable debug statements and asserts" OFF) 27 | if (DEBUG OR CMAKE_BUILD_TYPE STREQUAL "Debug") 28 | add_compile_definitions(DEBUG APP_ENABLE_ASSERTS APP_PROFILE) 29 | endif () 30 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Release") 2 | find_program(CLANGTIDY clang-tidy) 3 | if (CLANGTIDY) 4 | message(STATUS "Using clang-tidy") 5 | set(CMAKE_CXX_CLANG_TIDY "${CLANGTIDY};-extra-arg=-Wno-unknown-warning-option") 6 | # Explicitly enable exceptions on Windows 7 | if (WIN32) 8 | set(CMAKE_CXX_CLANG_TIDY "${CMAKE_CXX_CLANG_TIDY};--extra-arg=/EHsc") 9 | endif () 10 | else () 11 | message(WARNING "clang-tidy requested but executable not found") 12 | endif () 13 | 14 | if (NOT WIN32) 15 | message(STATUS "Using address sanitizer") 16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=address -g") 17 | endif () 18 | endif () 19 | -------------------------------------------------------------------------------- /cmake/UniversalAppleBuild.cmake: -------------------------------------------------------------------------------- 1 | # Generate universal executable for Apple hardware for Release builds. 2 | # This file needs to be included before calling `project`. 3 | if (APPLE AND "${CMAKE_GENERATOR}" STREQUAL "Xcode") 4 | # Universal build: 5 | # set(CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD)") 6 | # Build for current arch: 7 | set(CMAKE_OSX_ARCHITECTURES "$(NATIVE_ARCH_ACTUAL)") 8 | endif () 9 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(tests) 2 | add_subdirectory(app) 3 | add_subdirectory(some_library) 4 | -------------------------------------------------------------------------------- /src/app/App/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SomeLibrary/Core/Log.hpp" 4 | #include "SomeLibrary/Debug/Instrumentor.hpp" 5 | 6 | int main() { 7 | try { 8 | APP_PROFILE_BEGIN_SESSION_WITH_FILE("App", "profile.json"); 9 | 10 | { 11 | APP_PROFILE_SCOPE("Test scope"); 12 | APP_INFO("Hello World\n"); 13 | } 14 | 15 | APP_PROFILE_END_SESSION(); 16 | } catch (std::exception& e) { 17 | APP_ERROR("Main process terminated with: {}", e.what()); 18 | } 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(NAME "App") 2 | 3 | include(${PROJECT_SOURCE_DIR}/cmake/StaticAnalyzers.cmake) 4 | 5 | add_executable(${NAME} App/Main.cpp) 6 | 7 | install(TARGETS ${NAME} RUNTIME DESTINATION .) 8 | 9 | target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 10 | target_compile_features(${NAME} PRIVATE cxx_std_20) 11 | target_link_libraries(${NAME} PRIVATE project_warnings SomeLibrary) 12 | -------------------------------------------------------------------------------- /src/some_library/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(NAME "SomeLibrary") 2 | 3 | include(${PROJECT_SOURCE_DIR}/cmake/StaticAnalyzers.cmake) 4 | 5 | add_library(${NAME} STATIC 6 | SomeLibrary/Core/Log.cpp SomeLibrary/Core/Log.hpp 7 | SomeLibrary/Debug/Instrumentor.hpp) 8 | 9 | target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 10 | target_compile_features(${NAME} PRIVATE cxx_std_20) 11 | target_link_libraries(${NAME} PRIVATE project_warnings PUBLIC fmt spdlog) 12 | 13 | add_subdirectory(Tests) 14 | -------------------------------------------------------------------------------- /src/some_library/SomeLibrary/Core/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace App { 13 | 14 | Log::Log() { 15 | std::vector log_sinks; 16 | 17 | const spdlog::level::level_enum level{spdlog::level::debug}; 18 | 19 | log_sinks.emplace_back(std::make_shared()); 20 | log_sinks.emplace_back(std::make_shared("app.log", true)); 21 | 22 | log_sinks[0]->set_pattern("%^[%T] %n(%l): %v%$"); 23 | log_sinks[1]->set_pattern("[%T] [%l] %n(%l): %v"); 24 | 25 | m_logger = std::make_shared("APP", begin(log_sinks), end(log_sinks)); 26 | spdlog::register_logger(m_logger); 27 | spdlog::set_default_logger(m_logger); 28 | m_logger->set_level(level); 29 | m_logger->flush_on(level); 30 | } 31 | 32 | } // namespace App 33 | -------------------------------------------------------------------------------- /src/some_library/SomeLibrary/Core/Log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace App { 9 | 10 | class Log { 11 | public: 12 | Log(const Log&) = delete; 13 | Log(const Log&&) = delete; 14 | Log& operator=(const Log&) = delete; 15 | Log& operator=(const Log&&) = delete; 16 | ~Log() = default; 17 | 18 | static std::shared_ptr& logger() { 19 | return get().m_logger; 20 | } 21 | 22 | private: 23 | // The constructor shall not be deleted but used to bootstrap the logger. Ignoring 24 | // the lint warning is ignoring doing `Log() = delete`. 25 | // NOLINTNEXTLINE 26 | Log(); 27 | 28 | static Log& get() { 29 | static Log instance{}; 30 | return instance; 31 | } 32 | 33 | std::shared_ptr m_logger; 34 | }; 35 | 36 | } // namespace App 37 | 38 | #ifndef APP_DEACTIVATE_LOGGING 39 | 40 | #if DEBUG 41 | #define APP_TRACE(...) ::App::Log::logger()->trace(__VA_ARGS__) 42 | #define APP_DEBUG(...) ::App::Log::logger()->debug(__VA_ARGS__) 43 | #else 44 | #define APP_TRACE(...) 45 | #define APP_DEBUG(...) 46 | #endif 47 | 48 | #define APP_INFO(...) ::App::Log::logger()->info(__VA_ARGS__) 49 | #define APP_WARN(...) ::App::Log::logger()->warn(__VA_ARGS__) 50 | #define APP_ERROR(...) ::App::Log::logger()->error(__VA_ARGS__) 51 | #define APP_FATAL(...) ::App::Log::logger()->fatal(__VA_ARGS__) 52 | 53 | #else 54 | 55 | #define APP_TRACE(...) 56 | #define APP_DEBUG(...) 57 | #define APP_INFO(...) 58 | #define APP_WARN(...) 59 | #define APP_ERROR(...) 60 | #define APP_FATAL(...) 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/some_library/SomeLibrary/Debug/Instrumentor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "SomeLibrary/Core/Log.hpp" 13 | 14 | namespace App::Debug { 15 | 16 | using FloatingPointMicroseconds = std::chrono::duration; 17 | 18 | struct ProfileResult { 19 | std::string name; 20 | FloatingPointMicroseconds start; 21 | std::chrono::microseconds elapsed_time; 22 | std::thread::id thread_id; 23 | }; 24 | 25 | struct InstrumentationSession { 26 | std::string name; 27 | explicit InstrumentationSession(std::string session_name) : name(std::move(session_name)) {} 28 | }; 29 | 30 | class Instrumentor { 31 | public: 32 | Instrumentor(const Instrumentor&) = delete; 33 | Instrumentor(Instrumentor&&) = delete; 34 | Instrumentor& operator=(Instrumentor other) = delete; 35 | Instrumentor& operator=(Instrumentor&& other) = delete; 36 | 37 | void begin_session(const std::string& name, const std::string& filepath = "results.json") { 38 | const std::lock_guard lock(m_mutex); 39 | 40 | if (m_current_session != nullptr) { 41 | // If there is already a current session, then close it before beginning new one. 42 | // Subsequent profiling output meant for the original session will end up in the 43 | // newly opened session instead. That's better than having badly formatted 44 | // profiling output. 45 | APP_ERROR("Instrumentor::begin_session('{0}') when session '{1}' already open.", 46 | name, 47 | m_current_session->name); 48 | internal_end_session(); 49 | } 50 | m_output_stream.open(filepath); 51 | 52 | if (m_output_stream.is_open()) { 53 | m_current_session = std::make_unique(name); 54 | write_header(); 55 | } else { 56 | APP_ERROR("Instrumentor could not open results file '{0}'.", filepath); 57 | } 58 | } 59 | 60 | void end_session() { 61 | const std::lock_guard lock(m_mutex); 62 | internal_end_session(); 63 | } 64 | 65 | void write_profile(const ProfileResult& result) { 66 | std::stringstream json; 67 | 68 | std::string name{result.name}; 69 | std::replace(name.begin(), name.end(), '"', '\''); 70 | 71 | json << std::setprecision(3) << std::fixed; 72 | json << ",{"; 73 | json << R"("cat":"function",)"; 74 | json << "\"dur\":" << (result.elapsed_time.count()) << ','; 75 | json << R"("name":")" << name << "\","; 76 | json << R"("ph":"X",)"; 77 | json << "\"pid\":0,"; 78 | json << R"("tid":")" << result.thread_id << "\","; 79 | json << "\"ts\":" << result.start.count(); 80 | json << "}"; 81 | 82 | const std::lock_guard lock(m_mutex); 83 | if (m_current_session != nullptr) { 84 | m_output_stream << json.str(); 85 | m_output_stream.flush(); 86 | } 87 | } 88 | 89 | static Instrumentor& get() { 90 | static Instrumentor instance; 91 | return instance; 92 | } 93 | 94 | private: 95 | Instrumentor() : m_current_session(nullptr) {} 96 | 97 | ~Instrumentor() { 98 | end_session(); 99 | } 100 | 101 | void write_header() { 102 | m_output_stream << R"({"otherData": {},"traceEvents":[{})"; 103 | m_output_stream.flush(); 104 | } 105 | 106 | void write_footer() { 107 | m_output_stream << "]}"; 108 | m_output_stream.flush(); 109 | } 110 | 111 | // Note: you must already own lock on m_Mutex before 112 | // calling InternalEndSession() 113 | void internal_end_session() { 114 | if (m_current_session != nullptr) { 115 | write_footer(); 116 | m_output_stream.close(); 117 | } 118 | } 119 | 120 | std::mutex m_mutex; 121 | std::unique_ptr m_current_session; 122 | std::ofstream m_output_stream; 123 | }; 124 | 125 | class InstrumentationTimer { 126 | public: 127 | explicit InstrumentationTimer(std::string name) 128 | : m_name(std::move(name)), 129 | m_start_time_point(std::chrono::steady_clock::now()) {} 130 | 131 | InstrumentationTimer(const InstrumentationTimer&) = delete; 132 | InstrumentationTimer(InstrumentationTimer&&) = delete; 133 | InstrumentationTimer& operator=(InstrumentationTimer other) = delete; 134 | InstrumentationTimer& operator=(InstrumentationTimer&& other) = delete; 135 | 136 | ~InstrumentationTimer() { 137 | if (!m_stopped) { 138 | stop(); 139 | } 140 | } 141 | 142 | void stop() { 143 | const auto end_time_point{std::chrono::steady_clock::now()}; 144 | const auto high_res_start{FloatingPointMicroseconds{m_start_time_point.time_since_epoch()}}; 145 | const auto elapsed_time{ 146 | std::chrono::time_point_cast(end_time_point).time_since_epoch() - 147 | std::chrono::time_point_cast(m_start_time_point) 148 | .time_since_epoch()}; 149 | 150 | Instrumentor::get().write_profile( 151 | {m_name, high_res_start, elapsed_time, std::this_thread::get_id()}); 152 | 153 | m_stopped = true; 154 | } 155 | 156 | private: 157 | std::string m_name; 158 | bool m_stopped{false}; 159 | std::chrono::time_point m_start_time_point; 160 | }; 161 | 162 | } // namespace App::Debug 163 | 164 | #if APP_PROFILE 165 | // Resolve which function signature macro will be used. Note that this only 166 | // is resolved when the (pre)compiler starts, so the syntax highlighting 167 | // could mark the wrong one in your editor! 168 | #if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || \ 169 | (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__) 170 | // There is an implicit decay of an array into a pointer. 171 | // NOLINTNEXTLINE 172 | #define APP_FUNC_SIG __PRETTY_FUNCTION__ 173 | #elif defined(__DMC__) && (__DMC__ >= 0x810) 174 | #define APP_FUNC_SIG __PRETTY_FUNCTION__ 175 | #elif defined(__FUNCSIG__) 176 | #define APP_FUNC_SIG __FUNCSIG__ 177 | #elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || \ 178 | (defined(__IBMCPP__) && (__IBMCPP__ >= 500)) 179 | #define APP_FUNC_SIG __FUNCTION__ 180 | #elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550) 181 | #define APP_FUNC_SIG __FUNC__ 182 | #elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901) 183 | #define APP_FUNC_SIG __func__ 184 | #elif defined(__cplusplus) && (__cplusplus >= 201103) 185 | #define APP_FUNC_SIG __func__ 186 | #else 187 | #define APP_FUNC_SIG "APP_FUNC_SIG unknown!" 188 | #endif 189 | 190 | #define JOIN_AGAIN(x, y) x##y 191 | #define JOIN(x, y) JOIN_AGAIN(x, y) 192 | #define APP_PROFILE_BEGIN_SESSION(name) ::App::Debug::Instrumentor::get().begin_session(name) 193 | #define APP_PROFILE_BEGIN_SESSION_WITH_FILE(name, file_path) \ 194 | ::App::Debug::Instrumentor::get().begin_session(name, file_path) 195 | #define APP_PROFILE_END_SESSION() ::App::Debug::Instrumentor::get().end_session() 196 | #define APP_PROFILE_SCOPE(name) \ 197 | const ::App::Debug::InstrumentationTimer JOIN(timer, __LINE__) { \ 198 | name \ 199 | } 200 | #define APP_PROFILE_FUNCTION() APP_PROFILE_SCOPE(APP_FUNC_SIG) 201 | #else 202 | #define APP_PROFILE_BEGIN_SESSION(name) 203 | #define APP_PROFILE_BEGIN_SESSION_WITH_FILE(name, file_path) 204 | #define APP_PROFILE_END_SESSION() 205 | #define APP_PROFILE_SCOPE(name) 206 | #define APP_PROFILE_FUNCTION() 207 | #endif 208 | -------------------------------------------------------------------------------- /src/some_library/Tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Test cases 2 | 3 | add_executable(ResourcesTest Resources.spec.cpp $) 4 | add_test(NAME ResourcesTest COMMAND ResourcesTest) 5 | target_link_libraries(ResourcesTest PRIVATE doctest SomeLibrary) 6 | -------------------------------------------------------------------------------- /src/some_library/Tests/Resources.spec.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | // NOLINTBEGIN(misc-use-anonymous-namespace, cppcoreguidelines-avoid-do-while, cert-err33-c) 6 | 7 | TEST_SUITE("Core::Resources") { 8 | TEST_CASE("Example") { 9 | const std::string input{"A"}; 10 | CHECK_EQ(input, "A"); 11 | } 12 | } 13 | 14 | // NOLINTEND(misc-use-anonymous-namespace, cppcoreguidelines-avoid-do-while, cert-err33-c) 15 | -------------------------------------------------------------------------------- /src/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(NAME "TestRunner") 2 | 3 | add_library(${NAME} OBJECT ${PROJECT_SOURCE_DIR}/src/tests/TestRunner.cpp) 4 | target_compile_features(${NAME} PRIVATE cxx_std_20) 5 | target_link_libraries(${NAME} PUBLIC doctest) 6 | -------------------------------------------------------------------------------- /src/tests/TestRunner.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /vendor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | doctest 5 | GIT_REPOSITORY "https://github.com/onqtam/doctest.git" 6 | GIT_TAG v2.4.11 7 | ) 8 | add_subdirectory(doctest) 9 | 10 | FetchContent_Declare( 11 | fmt 12 | GIT_REPOSITORY "https://github.com/fmtlib/fmt.git" 13 | GIT_TAG 10.2.1 14 | ) 15 | add_subdirectory(fmt) 16 | 17 | FetchContent_Declare( 18 | spdlog 19 | GIT_REPOSITORY "https://github.com/gabime/spdlog.git" 20 | GIT_TAG v1.13.0 21 | ) 22 | add_subdirectory(spdlog) 23 | -------------------------------------------------------------------------------- /vendor/doctest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Fetching Doctest ...") 2 | 3 | set(DOCTEST_NO_INSTALL ON) 4 | 5 | FetchContent_MakeAvailable(doctest) 6 | -------------------------------------------------------------------------------- /vendor/fmt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Fetching fmt ...") 2 | 3 | option(FMT_INSTALL "Enable installation for the {fmt} project." OFF) 4 | 5 | FetchContent_MakeAvailable(fmt) 6 | -------------------------------------------------------------------------------- /vendor/spdlog/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Fetching spdlog ...") 2 | 3 | option(SPDLOG_FMT_EXTERNAL "ON") 4 | 5 | FetchContent_MakeAvailable(spdlog) 6 | --------------------------------------------------------------------------------