├── .appveyor.yml ├── .circleci └── config.yml ├── .clang-format ├── .clang-tidy ├── .cmake-format ├── .editorconfig ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── CMakePresets.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── cmake ├── Catch.cmake ├── CatchAddTests.cmake ├── FindASan.cmake ├── FindCMakeFormat.cmake ├── FindCppCheck.cmake ├── FindMSan.cmake ├── FindSanitizers.cmake ├── FindShellcheck.cmake ├── FindSphinx.cmake ├── FindTSan.cmake ├── FindUBSan.cmake ├── Findgcov.cmake ├── Findlcov.cmake ├── Gcov.cmake ├── GetGitRevisionDescription.cmake ├── GetGitRevisionDescription.cmake.in ├── MiscFunctions.cmake ├── README.md ├── asan-wrapper ├── clang-tidy.cmake ├── coverage.cmake ├── sanitize-helpers.cmake └── warnings.cmake ├── codecov.yml ├── conanfile.txt ├── data └── .gitkeep ├── docs ├── CMakeLists.txt ├── README.md ├── cpp-starter │ ├── README.md │ ├── cmake.md │ ├── continuous-deployment.md │ ├── continuous-integration.md │ ├── documentation.md │ ├── github-pages.md │ ├── linters.md │ ├── project-structure.md │ └── testing.md └── user │ ├── Makefile │ ├── create_changelog.sh │ ├── make.bat │ ├── publish_gh-pages.sh │ ├── release_notes_header.rst │ └── source │ ├── _static │ └── .gitkeep │ ├── about.rst │ ├── conf.py │ ├── contributing │ ├── bug-reports.rst │ └── index.rst │ ├── download.rst │ ├── faq.rst │ ├── index.rst │ └── license.rst ├── external ├── CMakeLists.txt └── clara │ └── clara.hpp ├── packaging ├── README.md └── description.txt ├── src ├── CMakeLists.txt ├── main.cpp ├── math │ ├── CMakeLists.txt │ ├── fibonacci.cpp │ └── fibonacci.hpp └── version.hpp.in ├── tests ├── CMakeLists.txt ├── main.cpp ├── math │ └── test_fibonacci.cpp └── test_helpers.hpp └── tools ├── CMakeLists.txt ├── README.md ├── build_run_clang_tidy.sh ├── quick_checks.sh ├── run_clang_format.sh ├── run_cmake_format.sh ├── run_cppcheck.sh ├── run_shellcheck.sh └── utils.sh /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | image: 4 | - Visual Studio 2019 5 | - Visual Studio 2022 6 | 7 | clone_folder: C:\projects\source 8 | clone_depth: 1 9 | 10 | environment: 11 | matrix: 12 | - generator: "Visual Studio 16 2019" 13 | init: 14 | - cmd: echo "%image%" 15 | - cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\%image%\Community\VC\Auxiliary\Build\vcvars64.bat" 16 | 17 | before_build: 18 | - cmd: set PATH=%PATH:C:\Program Files\Git\usr\bin;=% 19 | 20 | build_script: 21 | - cmd: echo "%image%" 22 | - cmd: echo "%generator%" 23 | - cmd: mkdir build 24 | - cmd: cd build 25 | - cmd: cmake -G "%generator%" -DCMAKE_BUILD_TYPE=Debug .. 26 | - cmd: cmake --build . --config "Debug" 27 | 28 | test_script: 29 | - cmd: ctest -C Debug 30 | 31 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/2.0/configuration-reference 3 | version: 2.1 4 | 5 | # Define a job to be invoked later in a workflow. 6 | # See: https://circleci.com/docs/2.0/configuration-reference/#jobs 7 | jobs: 8 | say-hello: 9 | # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. 10 | # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor 11 | docker: 12 | - image: cimg/base:stable 13 | # Add steps to the job 14 | # See: https://circleci.com/docs/2.0/configuration-reference/#steps 15 | steps: 16 | - checkout 17 | - run: 18 | name: "Say hello" 19 | command: "echo Hello, World!" 20 | 21 | # Invoke jobs via workflows 22 | # See: https://circleci.com/docs/2.0/configuration-reference/#workflows 23 | workflows: 24 | say-hello-workflow: 25 | jobs: 26 | - say-hello 27 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: WebKit 4 | ColumnLimit: 120 5 | FixNamespaceComments: true 6 | 7 | AlwaysBreakTemplateDeclarations: true 8 | AllowShortCaseLabelsOnASingleLine: true 9 | AllowShortFunctionsOnASingleLine: Empty 10 | BreakBeforeBraces: Custom 11 | BraceWrapping: 12 | AfterFunction: true 13 | AfterNamespace: false 14 | AfterStruct: false 15 | BeforeCatch: false 16 | BeforeElse: false 17 | SplitEmptyFunction: false 18 | SplitEmptyNamespace: true 19 | SplitEmptyRecord: false 20 | ... 21 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,bugprone-*,clang-*,cert-*,cppcoreguidelines-*,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,google-explicit-constructor,google-global-names-in-headers,google-readability-casting,llvm-*,-llvm-header-guard,-llvm-namespace-comment,misc-*,modernize-*,performance-*,-performance-unnecessary-value-param,readability-*,-cert-err58-cpp,-clang-analyzer-core.uninitialized.UndefReturn,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-avoid-magic-numbers,-cert-dcl16-c,-readability-uppercase-literal-suffix,-readability-magic-numbers,-modernize-use-trailing-return-type' 3 | WarningsAsErrors: '' 4 | AnalyzeTemporaryDtors: false 5 | FormatStyle: file 6 | CheckOptions: 7 | - key: bugprone-dangling-handle.HandleClasses 8 | value: 'std::basic_string_view;std::experimental::basic_string_view' 9 | - key: llvm-namespace-comment.ShortNamespaceLines 10 | value: '1' 11 | - key: llvm-namespace-comment.SpacesBeforeComments 12 | value: '1' 13 | ... 14 | 15 | -------------------------------------------------------------------------------- /.cmake-format: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # General Formatting Options 3 | # -------------------------- 4 | # How wide to allow formatted cmake files 5 | line_width = 80 6 | 7 | # How many spaces to tab for indent 8 | tab_size = 2 9 | 10 | # If an argument group contains more than this many sub-groups (parg or kwarg 11 | # groups), then force it to a vertical layout. 12 | max_subgroups_hwrap = 2 13 | 14 | # If a positinal argument group contains more than this many arguments, then 15 | # force it to a vertical layout. 16 | max_pargs_hwrap = 6 17 | 18 | # If true, separate flow control names from their parentheses with a space 19 | separate_ctrl_name_with_space = False 20 | 21 | # If true, separate function names from parentheses with a space 22 | separate_fn_name_with_space = False 23 | 24 | # If a statement is wrapped to more than one line, than dangle the closing 25 | # parenthesis on it's own line. 26 | dangle_parens = True 27 | 28 | # If the trailing parenthesis must be 'dangled' on it's on line, then align it 29 | # to this reference: `prefix`: the start of the statement, `prefix-indent`: the 30 | # start of the statement, plus one indentation level, `child`: align to the 31 | # column of the arguments 32 | dangle_align = 'prefix' 33 | 34 | min_prefix_chars = 4 35 | 36 | # If the statement spelling length (including space and parenthesis is larger 37 | # than the tab width by more than this amoung, then force reject un-nested 38 | # layouts. 39 | max_prefix_chars = 10 40 | 41 | # If a candidate layout is wrapped horizontally but it exceeds this many lines, 42 | # then reject the layout. 43 | max_lines_hwrap = 2 44 | 45 | # What style line endings to use in the output. 46 | line_ending = 'unix' 47 | 48 | # Format command names consistently as 'lower' or 'upper' case 49 | command_case = 'lower' 50 | 51 | # Format keywords consistently as 'lower' or 'upper' case 52 | keyword_case = 'upper' 53 | 54 | # Specify structure for custom cmake functions 55 | additional_commands = { 56 | "pkg_find": { 57 | "kwargs": { 58 | "PKG": "*" 59 | } 60 | } 61 | } 62 | 63 | # A list of command names which should always be wrapped 64 | always_wrap = [] 65 | 66 | # If true, the argument lists which are known to be sortable will be sorted 67 | # lexicographicall 68 | enable_sort = True 69 | 70 | # If true, the parsers may infer whether or not an argument list is sortable 71 | # (without annotation). 72 | autosort = True 73 | 74 | # If a comment line starts with at least this many consecutive hash characters, 75 | # then don't lstrip() them off. This allows for lazy hash rulers where the first 76 | # hash char is not separated by space 77 | hashruler_min_length = 10 78 | 79 | # A dictionary containing any per-command configuration overrides. Currently 80 | # only `command_case` is supported. 81 | per_command = {} 82 | 83 | # A dictionary mapping layout nodes to a list of wrap decisions. See the 84 | # documentation for more information. 85 | layout_passes = {} 86 | 87 | 88 | # -------------------------- 89 | # Comment Formatting Options 90 | # -------------------------- 91 | # What character to use for bulleted lists 92 | bullet_char = '*' 93 | 94 | # What character to use as punctuation after numerals in an enumerated list 95 | enum_char = '.' 96 | 97 | # enable comment markup parsing and reflow 98 | enable_markup = True 99 | 100 | # If comment markup is enabled, don't reflow the first comment block in each 101 | # listfile. Use this to preserve formatting of your copyright/license 102 | # statements. 103 | first_comment_is_literal = False 104 | 105 | # If comment markup is enabled, don't reflow any comment block which matches 106 | # this (regex) pattern. Default is `None` (disabled). 107 | literal_comment_pattern = None 108 | 109 | # Regular expression to match preformat fences in comments 110 | # default=r'^\s*([`~]{3}[`~]*)(.*)$' 111 | fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$' 112 | 113 | # Regular expression to match rulers in comments 114 | # default=r'^\s*[^\w\s]{3}.*[^\w\s]{3}$' 115 | ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' 116 | 117 | # If true, then insert a space between the first hash char and remaining hash 118 | # chars in a hash ruler, and normalize it's length to fill the column 119 | canonicalize_hashrulers = True 120 | 121 | 122 | # --------------------------------- 123 | # Miscellaneous Options 124 | # --------------------------------- 125 | # If true, emit the unicode byte-order mark (BOM) at the start of the file 126 | emit_byteorder_mark = False 127 | 128 | # Specify the encoding of the input file. Defaults to utf-8. 129 | input_encoding = 'utf-8' 130 | 131 | # Specify the encoding of the output file. Defaults to utf-8. Note that cmake 132 | # only claims to support utf-8 so be careful when using anything else 133 | output_encoding = 'utf-8' 134 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 4 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.yml] 17 | indent_size = 2 18 | 19 | [CMakeLists.txt] 20 | indent_size = 2 21 | 22 | [*.cmake] 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory 2 | build 3 | CMakeLists.txt.user* 4 | docs/user/source/release-notes.rst 5 | 6 | # IDEs 7 | .vscode/ 8 | .idea/ 9 | 10 | # Operating Systems 11 | .DS_Store 12 | 13 | # Prerequisites 14 | *.d 15 | 16 | # Compiled Object files 17 | *.slo 18 | *.lo 19 | *.o 20 | *.obj 21 | 22 | # Precompiled Headers 23 | *.gch 24 | *.pch 25 | 26 | # Compiled Dynamic libraries 27 | *.so 28 | *.dylib 29 | *.dll 30 | 31 | # Fortran module files 32 | *.mod 33 | *.smod 34 | 35 | # Compiled Static libraries 36 | *.lai 37 | *.la 38 | *.a 39 | *.lib 40 | 41 | # Executables 42 | *.exe 43 | *.out 44 | *.app 45 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # CI configuration for cpp-starter-project 2 | 3 | image: registry.gitlab.com/bugwelle/docker-modern-cpp-cmake:master 4 | 5 | stages: 6 | - check 7 | - build 8 | - test 9 | 10 | clang_format: 11 | stage: check 12 | script: 13 | - ./tools/run_clang_format.sh 14 | - git diff --diff-filter=M --color | cat 15 | - git diff --diff-filter=M --quiet || (echo "Found unformatted files! Use clang-format!"; exit 1) 16 | 17 | cmake_format: 18 | stage: check 19 | script: 20 | - ./tools/run_cmake_format.sh 21 | - git diff --diff-filter=M --color | cat 22 | - git diff --diff-filter=M --quiet || (echo "Found unformatted CMakeLists.txt! Use cmake-format!"; exit 1) 23 | 24 | cppcheck: 25 | stage: check 26 | script: 27 | - ./tools/run_cppcheck.sh 28 | 29 | clang_tidy: 30 | stage: build 31 | script: 32 | - ./tools/build_run_clang_tidy.sh 33 | - git diff --diff-filter=M --color | cat 34 | - git diff --diff-filter=M --quiet || (echo "Found fixable errors! Use clang-tidy!"; exit 1) 35 | 36 | build_project_release: 37 | stage: build 38 | script: 39 | - echo "GCC $(which g++)" 40 | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=$(which g++) 41 | - cmake --build build -j 2 --target cpp_test 42 | 43 | run_unit_tests: 44 | stage: test 45 | script: 46 | - echo "GCC $(which g++)" 47 | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=$(which g++) 48 | - cmake --build build -j 2 --target cpp_test 49 | - cd build && make test 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use minimal image as base to avoid overriding environment variables. 2 | language: minimal 3 | os: linux 4 | dist: focal 5 | 6 | stages: 7 | - check 8 | - test 9 | - coverage 10 | - name: deploy 11 | if: branch = main 12 | 13 | matrix: 14 | include: 15 | 16 | #---------------------------------------------------------------------------- 17 | # Check Stage 18 | # We run some linters and code formatters to check our code base. If this 19 | # already fails, we can skip others stages. 20 | #---------------------------------------------------------------------------- 21 | 22 | # Travis CI has integrated ShellCheck by default. 23 | - name: "shellcheck linting" 24 | stage: check 25 | language: shell 26 | before_script: 27 | - shellcheck --version 28 | script: 29 | - ./tools/run_shellcheck.sh 30 | 31 | - name: "cmake-format check" 32 | stage: check 33 | language: python 34 | python: ['3.8'] 35 | addons: { apt: { packages: ['python3-setuptools', 'python3-pip'] } } 36 | install: 37 | - pip install cmake_format 38 | before_script: 39 | - cmake-format --version 40 | script: 41 | - ./tools/run_cmake_format.sh 42 | - git diff --diff-filter=M --color | cat 43 | - git diff --diff-filter=M --quiet || (echo "Found unformatted CMakeLists.txt! Use cmake-format!"; exit 1) 44 | 45 | - name: "clang-tidy linting" 46 | stage: check 47 | env: CC=clang-12 CXX=clang++-12 48 | before_install: 49 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 50 | # install modern gcc for std lib 51 | - sudo apt install clang-12 clang-tidy-12 g++-11 52 | before_script: 53 | - cd tools 54 | script: 55 | - ./build_run_clang_tidy.sh 56 | - git diff --diff-filter=M --color | cat 57 | - git diff --diff-filter=M --quiet || (echo "Found fixable errors! Use clang-tidy!"; exit 1) 58 | 59 | - name: "cppcheck linting" 60 | stage: check 61 | addons: { apt: { packages: ['cppcheck'] } } 62 | before_script: 63 | - cppcheck --version 64 | script: 65 | - ./tools/run_cppcheck.sh 66 | 67 | #---------------------------------------------------------------------------- 68 | # Test Stage 69 | # Build and test our application on macOS and linux. All stages have the 70 | # script job in common (see end of this yml). 71 | #---------------------------------------------------------------------------- 72 | 73 | # macOS build and test using system's clang. Dependencies are installed 74 | # using the Homebrew addon, though "brew install ABC" works as well. 75 | - name: "macOS clang" 76 | stage: test 77 | os: osx 78 | osx_image: xcode10 79 | compiler: clang 80 | 81 | # Linux build and test using gcc-11. Dependencies are installed using the APT 82 | # addon, though "apt-get install ABC" works as well. 83 | - name: "linux build with GCC 11" 84 | stage: test 85 | env: CC=gcc-11 CXX=g++-11 86 | before_install: 87 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 88 | - sudo apt install gcc-11 g++-11 python3-setuptools python3-pip 89 | install: 90 | - pip3 install cmake 91 | 92 | - name: "linux build with GCC 10" 93 | stage: test 94 | env: CC=gcc-10 CXX=g++-10 95 | before_install: 96 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 97 | - sudo apt install gcc-10 g++-10 python3-setuptools python3-pip 98 | install: 99 | - pip3 install cmake 100 | 101 | # Windows build and test. Windows support is currently in beta on Travis. 102 | # CMake and MinGw are installed by default. 103 | # 104 | # TODO: Chocolatey has too many time outs. 105 | # 106 | # - name: "Windows GCC builds" 107 | # stage: test 108 | # os: windows 109 | # language: cpp 110 | # compiler: gcc 111 | # # env: 112 | # # - CMAKE_GENERATOR_OVERRIDE="Visual Studio 15 2017 Win64" 113 | # script: 114 | # - echo "CC=$CC" 115 | # - echo "CXX=$CXX" 116 | # - cmake -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" -DCMAKE_SH="CMAKE_SH-NOTFOUND" .. && 117 | # cmake --build . -j 2 && 118 | # ctest -j2 119 | 120 | #---------------------------------------------------------------------------- 121 | # Coverage Stage 122 | # Builds targets with "--coverage" and uses lcov to generate a coverage 123 | # report. 124 | #---------------------------------------------------------------------------- 125 | 126 | # Linux coverage build and test execution using gcc-11. 127 | - name: "linux coverage run" 128 | stage: coverage 129 | env: CC=gcc-11 CXX=g++-11 130 | before_install: 131 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 132 | - sudo apt install gcc-11 g++-11 python3-setuptools python3-pip 133 | install: 134 | # I had to many issues with older versions of lcov; use latest 135 | - git clone https://github.com/linux-test-project/lcov.git && 136 | ( cd lcov && sudo make install ) 137 | - sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-11 10 138 | - pip3 install cmake 139 | script: 140 | - cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON && 141 | make -j$(nproc) && 142 | make coverage 143 | - bash <(curl -s https://codecov.io/bash) -f coverage-filtered.info || echo "Codecov did not collect coverage reports" 144 | 145 | before_script: 146 | - mkdir build && cd $_ 147 | 148 | script: 149 | - cmake -DCMAKE_BUILD_TYPE=Debug .. && 150 | cmake --build . -j $(nproc) --config "Debug" && 151 | ctest -j2 152 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Since previous release 4 | 5 | - Preliminary support for fix suggestions 6 | 7 | 8 | ## v0.1.0 - 2018-12-18 9 | 10 | - First release (#1) 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16 FATAL_ERROR) 2 | 3 | project( 4 | cpp_starter 5 | VERSION 0.2.0 6 | DESCRIPTION "C++ Starter Project" 7 | HOMEPAGE_URL "https://github.com/bugwelle/cpp-starter-project" 8 | LANGUAGES CXX 9 | ) 10 | 11 | message("=> Project: ${PROJECT_NAME}") 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Project configuration options. Sanitizer options are defined in the 15 | # correspondig FindXX modules. 16 | # cmake-format: off 17 | option(ENABLE_CLANG_TIDY "Analyze code with clang-tidy." OFF) 18 | option(ENABLE_CLANG_TIDY_FIX "Analyze code with clang-tidy and fix errors." OFF) 19 | option(ENABLE_COVERAGE "Add coverage information to binaries." OFF) 20 | # cmake-format: on 21 | 22 | # ----------------------------------------------------------------------------- 23 | # CMake modules and other cmake related (third-party) scripts 24 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") 25 | 26 | include(MiscFunctions) 27 | check_no_in_source_build() 28 | set_default_build_type("RelWithDebInfo") 29 | 30 | # for Thread/Address/Memory/UB Sanitizers 31 | find_package(Sanitizers) 32 | 33 | include(GNUInstallDirs) 34 | include(FetchContent) 35 | include(GenerateExportHeader) # TODO: WHY? 36 | include(warnings) # custom function that enables compiler warnings 37 | include(GetGitRevisionDescription) 38 | include(coverage) # for lcov and gcov code coverage 39 | include(clang-tidy) 40 | 41 | # ----------------------------------------------------------------------------- 42 | # Get git revision 43 | get_git_head_revision(GIT_REFSPEC GIT_SHA1) 44 | string(SUBSTRING "${GIT_SHA1}" 0 12 GIT_REV) 45 | if(NOT GIT_SHA1) 46 | set(GIT_REV "0") 47 | endif() 48 | 49 | activate_coverage(ENABLE_COVERAGE) 50 | 51 | # ----------------------------------------------------------------------------- 52 | # Some defaults for our targets. Currently warnings are enabled and the C++ 53 | # standard is set to C++20. It simplifies handling multiple targets like 54 | # different libraries without having to repeat all compile-features, etc. Call 55 | # this function after *all* targets have been added to ${target}! 56 | # 57 | # TODO: Can we now use a target and link against it instead? 58 | function(set_post_target_defaults target) 59 | if(NOT TARGET ${target}) 60 | message(WARNING "cpp-starter defaults: ${target} is not a target.") 61 | return() 62 | endif() 63 | target_compile_features(${target} PUBLIC cxx_std_20) 64 | # We want a better project structure in some IDEs like Visual Studio. See 65 | # https://cmake.org/cmake/help/latest/prop_tgt/FOLDER.html 66 | set_target_properties(${target} PROPERTIES FOLDER "${PROJECT_NAME}") 67 | target_include_directories( 68 | ${target} PUBLIC ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src 69 | ) 70 | enable_warnings(${target}) 71 | target_enable_coverage(${target}) 72 | add_sanitizers(${target}) 73 | endfunction() 74 | 75 | # ----------------------------------------------------------------------------- 76 | add_subdirectory(docs) 77 | add_subdirectory(external) # third-party targets (e.g. interface targets for 78 | # header-only libraries) 79 | add_subdirectory(src) 80 | add_subdirectory(tools) # Tools like cppcheck and cmake-format 81 | 82 | # ----------------------------------------------------------------------------- 83 | # Tests using Catch2 and CTest 84 | include(CTest) 85 | include(Catch) 86 | enable_testing() # Per CMake documentation, enable_testing() must be called in 87 | # the root directory. 88 | add_subdirectory(tests) 89 | 90 | # ----------------------------------------------------------------------------- 91 | # Packaging 92 | 93 | include(InstallRequiredSystemLibraries) 94 | 95 | # See https://cmake.org/cmake/help/latest/module/CPack.html#variables-common-to-all-cpack-generators 96 | set(CPACK_PACKAGE_VENDOR "bugwelle") 97 | set(CPACK_PACKAGE_CHECKSUM "SHA512") 98 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_LIST_DIR}/packaging/description.txt") 99 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_LIST_DIR}/LICENSE") 100 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_LIST_DIR}/README.md") 101 | set(CPACK_PACKAGE_EXECUTABLES "cpp_starter;C++ Starter") 102 | set(CPACK_VERBATIM_VARIABLES True) 103 | set(CPACK_THREADS 0) 104 | 105 | # Specific to macOS 106 | set(CPACK_BUNDLE_NAME "C++ Starter") 107 | 108 | include(CPack) 109 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "configurePresets": [ 4 | { 5 | "name": "release", 6 | "displayName": "release", 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build/${presetName}", 9 | "cacheVariables": { 10 | "CMAKE_BUILD_TYPE": "Release" 11 | } 12 | }, 13 | { 14 | "name": "debug", 15 | "displayName": "debug", 16 | "generator": "Ninja", 17 | "binaryDir": "${sourceDir}/build/${presetName}", 18 | "cacheVariables": { 19 | "CMAKE_BUILD_TYPE": "Debug", 20 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", 21 | "CMAKE_CXX_COMPILER_LAUNCHER": "ccache" 22 | } 23 | } 24 | ], 25 | "buildPresets": [ 26 | { 27 | "name": "release", 28 | "configurePreset": "release" 29 | }, 30 | { 31 | "name": "debug", 32 | "configurePreset": "debug" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Project Requirements 4 | 5 | This project uses many (modern) tools, so you may need to install quite a lot first. 6 | 7 | You need to install: 8 | 9 | - [CMake](https://cmake.org/) 10 | - [Visual Studio 15](https://visualstudio.microsoft.com/) or 11 | [MinGW 64](https://mingw-w64.org/doku.php) 12 | 13 | Following tools are optional but recommended: 14 | - [Doxygen](http://www.doxygen.nl/) 15 | - [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) 16 | - [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) 17 | - [`cmake-format`](https://github.com/cheshirekow/cmake_format) 18 | - [Cppcheck](http://cppcheck.sourceforge.net/) 19 | 20 | ### Install Requirements on Ubuntu 21 | 22 | These instructions were tested on Ubuntu 18.04: 23 | 24 | ```sh 25 | pip3 install cmake cmake_format 26 | sudo apt install doxygen doxygen-doc 27 | sudo apt install shellcheck 28 | sudo apt install gcc g++ 29 | sudo apt install clang clang++ clang-format clang-tidy 30 | ``` 31 | 32 | ## CMake Targets 33 | 34 | Following build targets are available: 35 | 36 | ```sh 37 | make # Build all targets 38 | make cmake-format # Format all CMake files according to .cmake-format 39 | make coverage # Create code coverage 40 | make cppcheck # Run a linter for C++ 41 | make docs # Create user documentation 42 | make doxygen # Create developer documentation 43 | make shellcheck # Lints all bash scripts 44 | make test # Run all tests (`ctest` works as well) 45 | ``` 46 | 47 | ## Building 48 | 49 | ### Linux & macOS 50 | 51 | ```sh 52 | cd cpp-starter-project 53 | mkdir build && cd $_ 54 | cmake -DCMAKE_BUILD_TYPE=Debug .. 55 | make -j$(nproc) 56 | ``` 57 | 58 | ### Windows 59 | 60 | ```sh 61 | cd cpp-starter-project 62 | mkdir build 63 | cd build 64 | cmake -DCMAKE_BUILD_TYPE=Debug -G "Visual Studio 15 2017 Win64" .. # or "MinGW Makefiles" 65 | cmake --build . --config "Debug" 66 | ``` 67 | 68 | ## Testing 69 | 70 | ```sh 71 | # Test 72 | cmake .. 73 | # Note: CMake >= 3.12 supports -jN, so we don't need to pass to to `make` 74 | cmake --build . -j 2 75 | ctest . 76 | 77 | # Create coverage 78 | cmake -DENABLE_COVERAGE:BOOL=ON .. 79 | cmake --build . -j 2 -- coverage 80 | ``` 81 | 82 | ## Sanitizers 83 | 84 | This project can be build using multiple sanitizers. 85 | Just pass one of these options to CMake. 86 | 87 | ```sh 88 | cmake -DSANITIZE_ADDRESS=ON .. 89 | cmake -DSANITIZE_THREAD=ON .. 90 | cmake -DSANITIZE_UNDEFINED=ON .. 91 | CXX=clang++ CC=clang cmake -DSANITIZE_MEMORY=ON .. 92 | ``` 93 | 94 | The memory sanitizer is as of now (2018-12-18) only available for clang. 95 | 96 | ## Branching policy 97 | 98 | Currently, all work is done on the `main` branch. 99 | For each change that you want to submit by creating a new pull requests, 100 | please create a new branch that has a meaningful name, e.g. `fix-memory-leak` 101 | or `fix-nn` where `nn` is the id of an GitHub issue. 102 | 103 | ## Submitting code 104 | 105 | Submitted code should follow these quality guidelines: 106 | 107 | - all Travis CI stages pass 108 | - all GitLab CI jobs pass 109 | - all AppVeyor jobs pass 110 | - code should adhere to project standards 111 | 112 | If anything breaks, you'll get notified on each pull request. 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2021 Andre Meyering 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 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/d6cd38c4a97341f79b1712548444e703)](https://www.codacy.com/gh/bugwelle/cpp-starter-project/dashboard?utm_source=github.com&utm_medium=referral&utm_content=bugwelle/cpp-starter-project&utm_campaign=Badge_Grade) 2 | [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 3 | [![Travis CI Build Status](https://app.travis-ci.com/bugwelle/cpp-starter-project.svg?branch=main)](https://app.travis-ci.com/bugwelle/cpp-starter-project) 4 | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/j56v8bgwtk24tuu9/branch/main?svg=true)](https://ci.appveyor.com/project/archer96/cpp-starter-project/branch/main)[![GitLab CI Pipeline Status](https://img.shields.io/gitlab/pipeline/bugwelle/cpp-starter-project.svg?logo=gitlab)](https://gitlab.com/bugwelle/cpp-starter-project/pipelines) 5 | [![codecov](https://codecov.io/gh/bugwelle/cpp-starter-project/branch/main/graph/badge.svg)](https://codecov.io/gh/bugwelle/cpp-starter-project) 6 | 7 | 8 | # cpp-starter-project 9 | 10 | **This project is not finished, yet!** 11 | **Even if this readme says that it uses/supports X, that may not be the case, yet!** 12 | 13 | `cpp-starter-project` is a personal starter project for modern C++ development. 14 | Its goal is to offer a set of modern tools and resources to interested 15 | developers (e.g. students) that want to create executable programs. This 16 | project is not intended for creating libraries. 17 | 18 | ## Why? 19 | I found many C++ starter projects and even more tutorials that give a 20 | good introduction into starting new C++ and [CMake][cmake] projects but none 21 | of them were either up-to-date (speaking C++20 and CMake >= 3.12) or had all 22 | features that I'd like to have like package managers or continuous integration. 23 | 24 | 25 | ## Documentation 26 | Documentation about how this starter project is structured and which continuous 27 | integration services, etc. are used can be found in 28 | [`docs/cpp-starter`](docs/cpp-starter/README.md). 29 | 30 | Generated Sphinx documentation can be found at 31 | 32 | 33 | ## Features 34 | This project aims to be a full-fledged entry point for new medium-sized projects. 35 | Following features are included: 36 | 37 | - [x] project structure common to C++ projects (similar to [pitchfork][pf]) 38 | - [x] modern C++ (currently C++20) 39 | - [x] modern CMake >= 3.12 40 | (no more `include_directories(...)` or `link_libraries(...)`) 41 | - [x] testing framework ([Catch2][catch2]) 42 | - [x] user documentation using [Doxygen][doxygen] and [Sphinx][sphinx] 43 | - [x] documentation using [Doxygen][doxygen] and [Sphinx][sphinx] 44 | - [x] [GitHub Pages][ghpages] on `gh-pages` branch 45 | - [x] code formatting because no one has got time for that in code reviews 46 | ([`clang-format`][clangfmt] and [`cmake-format`][cmakefmt]) 47 | - [x] static code analysis tools like linters 48 | ([`cppcheck`](cppcheck), [`clang-tidy`][clangtidy], [`shellcheck`][shcheck]) 49 | - [x] continuous integration: useful not only for pull requests 50 | ([Travis CI][travis], [AppVeyor][appveyor], [GitLab CI][gitlabci]) 51 | - [ ] continuous deployment to [GitHub Pages][githubpg] 52 | - [x] code coverage using [Codecov][codecov] 53 | - [ ] CPack script for packaging 54 | 55 | ## What's not included? 56 | This project no longer uses Conan as package manager. I've also not included VCPKG or other package 57 | managers. None are perfect, all have their drawbacks. If you want to use Conan, you can still do so. 58 | Please refer to their "[Getting started](https://docs.conan.io/en/latest/getting_started.html)" guide. 59 | 60 | ## Inspiration 61 | This project was inspired by: 62 | 63 | - https://github.com/abdes/asap 64 | - https://github.com/lefticus/cpp_starter_project 65 | - https://github.com/ttroy50/cmake-examples 66 | - https://github.com/kartikkumar/cppbase 67 | - https://github.com/richelbilderbeek/travis_cpp_tutorial 68 | - https://github.com/codecov/example-cpp11-cmake 69 | - https://github.com/cginternals/cmake-init 70 | - https://github.com/LearningByExample/ModernCppCI 71 | (https://juan-medina.com/2017/07/01/moderncppci/) 72 | 73 | 74 | ## License 75 | See [`LICENSE`](LICENSE). This project contains third-party software like CMake 76 | modules and C++ libraries. See section "Third Party". 77 | 78 | 79 | ## Third Party 80 | This project uses: 81 | 82 | - Catch2 83 | - Website: https://github.com/catchorg/Catch2 84 | - License: [Boost Software License 1.0](https://github.com/catchorg/Catch2/blob/master/LICENSE.txt) 85 | - Reason: Testing framework 86 | - range-v3 87 | - Website: https://github.com/ericniebler/range-v3 88 | - License: [Boost Software License 1.0](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt) 89 | - Reason: C++ ranges implementation 90 | 91 | We also use some CMake modules. See `cmake/README.md`. 92 | 93 | 94 | ## Notes 95 | Because we use range-v3 and other modern C++ features, we only support these compilers: 96 | 97 | - Windows 98 | - MSVC 2017 version >= 15.9 99 | - TODO: MinGW64 100 | - macOS 101 | - TODO: clang 102 | - TODO: GCC 103 | - linux 104 | - TODO: clang 105 | - TODO: GCC 106 | 107 | [appveyor]: https://ci.appveyor.com/project/archer96/cpp-starter-project 108 | [catch2]: https://github.com/catchorg/Catch2 109 | [clangfmt]: https://clang.llvm.org/docs/ClangFormat.html 110 | [clangtidy]: https://clang.llvm.org/extra/clang-tidy/ 111 | [cmake]: https://cmake.org/ 112 | [cmakefmt]: https://github.com/cheshirekow/cmake_format 113 | [codecov]: https://codecov.io/ 114 | [cppcheck]: http://cppcheck.sourceforge.net/ 115 | [doxygen]: http://www.doxygen.nl/ 116 | [ghpages]: https://bugwelle.github.io/cpp-starter-project/ 117 | [githubpg]: https://github.com/bugwelle/cpp-starter-project/releases 118 | [gitlabci]: https://gitlab.com/bugwelle/cpp-starter-project/pipelines 119 | [pf]: https://github.com/vector-of-bool/pitchfork 120 | [shcheck]: https://www.shellcheck.net/ 121 | [sphinx]: http://www.sphinx-doc.org/en/stable/ 122 | [travis]: https://travis-ci.org/bugwelle/cpp-starter-project 123 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - main 8 | 9 | pool: 10 | vmImage: ubuntu-latest 11 | 12 | steps: 13 | - script: echo Hello, world! 14 | displayName: 'Run a one-line script' 15 | 16 | - script: | 17 | echo Add other tasks to build, test, and deploy your project. 18 | echo See https://aka.ms/yaml 19 | displayName: 'Run a multi-line script' 20 | -------------------------------------------------------------------------------- /cmake/Catch.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 3 | # file Copyright.txt or https://cmake.org/licensing for details. 4 | 5 | #[=======================================================================[.rst: 6 | Catch 7 | ----- 8 | 9 | This module defines a function to help use the Catch test framework. 10 | 11 | The :command:`catch_discover_tests` discovers tests by asking the compiled test 12 | executable to enumerate its tests. This does not require CMake to be re-run 13 | when tests change. However, it may not work in a cross-compiling environment, 14 | and setting test properties is less convenient. 15 | 16 | This command is intended to replace use of :command:`add_test` to register 17 | tests, and will create a separate CTest test for each Catch test case. Note 18 | that this is in some cases less efficient, as common set-up and tear-down logic 19 | cannot be shared by multiple test cases executing in the same instance. 20 | However, it provides more fine-grained pass/fail information to CTest, which is 21 | usually considered as more beneficial. By default, the CTest test name is the 22 | same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. 23 | 24 | .. command:: catch_discover_tests 25 | 26 | Automatically add tests with CTest by querying the compiled test executable 27 | for available tests:: 28 | 29 | catch_discover_tests(target 30 | [TEST_SPEC arg1...] 31 | [EXTRA_ARGS arg1...] 32 | [WORKING_DIRECTORY dir] 33 | [TEST_PREFIX prefix] 34 | [TEST_SUFFIX suffix] 35 | [PROPERTIES name1 value1...] 36 | [TEST_LIST var] 37 | ) 38 | 39 | ``catch_discover_tests`` sets up a post-build command on the test executable 40 | that generates the list of tests by parsing the output from running the test 41 | with the ``--list-test-names-only`` argument. This ensures that the full 42 | list of tests is obtained. Since test discovery occurs at build time, it is 43 | not necessary to re-run CMake when the list of tests changes. 44 | However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set 45 | in order to function in a cross-compiling environment. 46 | 47 | Additionally, setting properties on tests is somewhat less convenient, since 48 | the tests are not available at CMake time. Additional test properties may be 49 | assigned to the set of tests as a whole using the ``PROPERTIES`` option. If 50 | more fine-grained test control is needed, custom content may be provided 51 | through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` 52 | directory property. The set of discovered tests is made accessible to such a 53 | script via the ``_TESTS`` variable. 54 | 55 | The options are: 56 | 57 | ``target`` 58 | Specifies the Catch executable, which must be a known CMake executable 59 | target. CMake will substitute the location of the built executable when 60 | running the test. 61 | 62 | ``TEST_SPEC arg1...`` 63 | Specifies test cases, wildcarded test cases, tags and tag expressions to 64 | pass to the Catch executable with the ``--list-test-names-only`` argument. 65 | 66 | ``EXTRA_ARGS arg1...`` 67 | Any extra arguments to pass on the command line to each test case. 68 | 69 | ``WORKING_DIRECTORY dir`` 70 | Specifies the directory in which to run the discovered test cases. If this 71 | option is not provided, the current binary directory is used. 72 | 73 | ``TEST_PREFIX prefix`` 74 | Specifies a ``prefix`` to be prepended to the name of each discovered test 75 | case. This can be useful when the same test executable is being used in 76 | multiple calls to ``catch_discover_tests()`` but with different 77 | ``TEST_SPEC`` or ``EXTRA_ARGS``. 78 | 79 | ``TEST_SUFFIX suffix`` 80 | Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of 81 | every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may 82 | be specified. 83 | 84 | ``PROPERTIES name1 value1...`` 85 | Specifies additional properties to be set on all tests discovered by this 86 | invocation of ``catch_discover_tests``. 87 | 88 | ``TEST_LIST var`` 89 | Make the list of tests available in the variable ``var``, rather than the 90 | default ``_TESTS``. This can be useful when the same test 91 | executable is being used in multiple calls to ``catch_discover_tests()``. 92 | Note that this variable is only available in CTest. 93 | 94 | #]=======================================================================] 95 | 96 | #------------------------------------------------------------------------------ 97 | function(catch_discover_tests TARGET) 98 | cmake_parse_arguments( 99 | "" 100 | "" 101 | "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" 102 | "TEST_SPEC;EXTRA_ARGS;PROPERTIES" 103 | ${ARGN} 104 | ) 105 | 106 | if(NOT _WORKING_DIRECTORY) 107 | set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") 108 | endif() 109 | if(NOT _TEST_LIST) 110 | set(_TEST_LIST ${TARGET}_TESTS) 111 | endif() 112 | 113 | ## Generate a unique name based on the extra arguments 114 | string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") 115 | string(SUBSTRING ${args_hash} 0 7 args_hash) 116 | 117 | # Define rule to generate test list for aforementioned test executable 118 | set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") 119 | set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") 120 | get_property(crosscompiling_emulator 121 | TARGET ${TARGET} 122 | PROPERTY CROSSCOMPILING_EMULATOR 123 | ) 124 | add_custom_command( 125 | TARGET ${TARGET} POST_BUILD 126 | BYPRODUCTS "${ctest_tests_file}" 127 | COMMAND "${CMAKE_COMMAND}" 128 | -D "TEST_TARGET=${TARGET}" 129 | -D "TEST_EXECUTABLE=$" 130 | -D "TEST_EXECUTOR=${crosscompiling_emulator}" 131 | -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" 132 | -D "TEST_SPEC=${_TEST_SPEC}" 133 | -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" 134 | -D "TEST_PROPERTIES=${_PROPERTIES}" 135 | -D "TEST_PREFIX=${_TEST_PREFIX}" 136 | -D "TEST_SUFFIX=${_TEST_SUFFIX}" 137 | -D "TEST_LIST=${_TEST_LIST}" 138 | -D "CTEST_FILE=${ctest_tests_file}" 139 | -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" 140 | VERBATIM 141 | ) 142 | 143 | file(WRITE "${ctest_include_file}" 144 | "if(EXISTS \"${ctest_tests_file}\")\n" 145 | " include(\"${ctest_tests_file}\")\n" 146 | "else()\n" 147 | " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" 148 | "endif()\n" 149 | ) 150 | 151 | if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") 152 | # Add discovered tests to directory TEST_INCLUDE_FILES 153 | set_property(DIRECTORY 154 | APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" 155 | ) 156 | else() 157 | # Add discovered tests as directory TEST_INCLUDE_FILE if possible 158 | get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) 159 | if (NOT ${test_include_file_set}) 160 | set_property(DIRECTORY 161 | PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" 162 | ) 163 | else() 164 | message(FATAL_ERROR 165 | "Cannot set more than one TEST_INCLUDE_FILE" 166 | ) 167 | endif() 168 | endif() 169 | 170 | endfunction() 171 | 172 | ############################################################################### 173 | 174 | set(_CATCH_DISCOVER_TESTS_SCRIPT 175 | ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake 176 | ) 177 | # cmake-format: on 178 | -------------------------------------------------------------------------------- /cmake/CatchAddTests.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 3 | # file Copyright.txt or https://cmake.org/licensing for details. 4 | 5 | set(prefix "${TEST_PREFIX}") 6 | set(suffix "${TEST_SUFFIX}") 7 | set(spec ${TEST_SPEC}) 8 | set(extra_args ${TEST_EXTRA_ARGS}) 9 | set(properties ${TEST_PROPERTIES}) 10 | set(script) 11 | set(suite) 12 | set(tests) 13 | 14 | function(add_command NAME) 15 | set(_args "") 16 | foreach(_arg ${ARGN}) 17 | if(_arg MATCHES "[^-./:a-zA-Z0-9_]") 18 | set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument 19 | else() 20 | set(_args "${_args} ${_arg}") 21 | endif() 22 | endforeach() 23 | set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) 24 | endfunction() 25 | 26 | # Run test executable to get list of available tests 27 | if(NOT EXISTS "${TEST_EXECUTABLE}") 28 | message(FATAL_ERROR 29 | "Specified test executable '${TEST_EXECUTABLE}' does not exist" 30 | ) 31 | endif() 32 | execute_process( 33 | COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only 34 | OUTPUT_VARIABLE output 35 | RESULT_VARIABLE result 36 | ) 37 | # Catch --list-test-names-only reports the number of tests, so 0 is... surprising 38 | if(${result} EQUAL 0) 39 | message(WARNING 40 | "Test executable '${TEST_EXECUTABLE}' contains no tests!\n" 41 | ) 42 | elseif(${result} LESS 0) 43 | message(FATAL_ERROR 44 | "Error running test executable '${TEST_EXECUTABLE}':\n" 45 | " Result: ${result}\n" 46 | " Output: ${output}\n" 47 | ) 48 | endif() 49 | 50 | string(REPLACE "\n" ";" output "${output}") 51 | 52 | # Parse output 53 | foreach(line ${output}) 54 | set(test ${line}) 55 | # ...and add to script 56 | add_command(add_test 57 | "${prefix}${test}${suffix}" 58 | ${TEST_EXECUTOR} 59 | "${TEST_EXECUTABLE}" 60 | "${test}" 61 | ${extra_args} 62 | ) 63 | add_command(set_tests_properties 64 | "${prefix}${test}${suffix}" 65 | PROPERTIES 66 | WORKING_DIRECTORY "${TEST_WORKING_DIR}" 67 | ${properties} 68 | ) 69 | list(APPEND tests "${prefix}${test}${suffix}") 70 | endforeach() 71 | 72 | # Create a list of all discovered tests, which users may use to e.g. set 73 | # properties on the tests 74 | add_command(set ${TEST_LIST} ${tests}) 75 | 76 | # Write CTest script 77 | file(WRITE "${CTEST_FILE}" "${script}") 78 | # cmake-format: on 79 | -------------------------------------------------------------------------------- /cmake/FindASan.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 5 | # 2013 Matthew Arsenault 6 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off) 27 | 28 | set(FLAG_CANDIDATES 29 | # Clang 3.2+ use this version. The no-omit-frame-pointer option is optional. 30 | "-g -fsanitize=address -fno-omit-frame-pointer" 31 | "-g -fsanitize=address" 32 | 33 | # Older deprecated flag for ASan 34 | "-g -faddress-sanitizer" 35 | ) 36 | 37 | 38 | if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY)) 39 | message(FATAL_ERROR "AddressSanitizer is not compatible with " 40 | "ThreadSanitizer or MemorySanitizer.") 41 | endif () 42 | 43 | 44 | include(sanitize-helpers) 45 | 46 | if (SANITIZE_ADDRESS) 47 | sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer" 48 | "ASan") 49 | 50 | find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH}) 51 | mark_as_advanced(ASan_WRAPPER) 52 | endif () 53 | 54 | function (add_sanitize_address TARGET) 55 | if (NOT SANITIZE_ADDRESS) 56 | return() 57 | endif () 58 | 59 | sanitizer_add_flags(${TARGET} "AddressSanitizer" "ASan") 60 | endfunction () 61 | 62 | # cmake-format: on 63 | -------------------------------------------------------------------------------- /cmake/FindCMakeFormat.cmake: -------------------------------------------------------------------------------- 1 | # https://github.com/cheshirekow/cmake_format 2 | find_program( 3 | CMAKE_FORMAT_EXE 4 | NAMES cmake-format 5 | PATHS /usr/bin/ 6 | PATH_SUFFIXES bin 7 | DOC "cmake formater" 8 | ) 9 | 10 | include(FindPackageHandleStandardArgs) 11 | 12 | find_package_handle_standard_args(CMakeFormat DEFAULT_MSG CMAKE_FORMAT_EXE) 13 | 14 | mark_as_advanced(CMAKE_FORMAT_EXE) 15 | -------------------------------------------------------------------------------- /cmake/FindCppCheck.cmake: -------------------------------------------------------------------------------- 1 | find_program( 2 | CPPCHECK_EXE 3 | NAMES cppcheck 4 | PATHS /usr/bin/ 5 | PATH_SUFFIXES bin 6 | DOC "static code analysis tool" 7 | ) 8 | 9 | include(FindPackageHandleStandardArgs) 10 | find_package_handle_standard_args(CppCheck DEFAULT_MSG CPPCHECK_EXE) 11 | 12 | mark_as_advanced(CPPCHECK_EXE) 13 | -------------------------------------------------------------------------------- /cmake/FindMSan.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 5 | # 2013 Matthew Arsenault 6 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off) 27 | 28 | set(FLAG_CANDIDATES 29 | "-g -fsanitize=memory" 30 | ) 31 | 32 | 33 | include(sanitize-helpers) 34 | 35 | if (SANITIZE_MEMORY) 36 | if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 37 | message(WARNING "MemorySanitizer disabled for target ${TARGET} because " 38 | "MemorySanitizer is supported for Linux systems only.") 39 | set(SANITIZE_MEMORY Off CACHE BOOL 40 | "Enable MemorySanitizer for sanitized targets." FORCE) 41 | elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) 42 | message(WARNING "MemorySanitizer disabled for target ${TARGET} because " 43 | "MemorySanitizer is supported for 64bit systems only.") 44 | set(SANITIZE_MEMORY Off CACHE BOOL 45 | "Enable MemorySanitizer for sanitized targets." FORCE) 46 | else () 47 | sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer" 48 | "MSan") 49 | endif () 50 | endif () 51 | 52 | function (add_sanitize_memory TARGET) 53 | if (NOT SANITIZE_MEMORY) 54 | return() 55 | endif () 56 | 57 | sanitizer_add_flags(${TARGET} "MemorySanitizer" "MSan") 58 | endfunction () 59 | 60 | # cmake-format: on 61 | -------------------------------------------------------------------------------- /cmake/FindSanitizers.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 5 | # 2013 Matthew Arsenault 6 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | # If any of the used compiler is a GNU compiler, add a second option to static 27 | # link against the sanitizers. 28 | option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off) 29 | 30 | 31 | 32 | 33 | set(FIND_QUIETLY_FLAG "") 34 | if (DEFINED Sanitizers_FIND_QUIETLY) 35 | set(FIND_QUIETLY_FLAG "QUIET") 36 | endif () 37 | 38 | find_package(ASan ${FIND_QUIETLY_FLAG}) 39 | find_package(TSan ${FIND_QUIETLY_FLAG}) 40 | find_package(MSan ${FIND_QUIETLY_FLAG}) 41 | find_package(UBSan ${FIND_QUIETLY_FLAG}) 42 | 43 | 44 | 45 | 46 | function(sanitizer_add_blacklist_file FILE) 47 | if(NOT IS_ABSOLUTE ${FILE}) 48 | set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}") 49 | endif() 50 | get_filename_component(FILE "${FILE}" REALPATH) 51 | 52 | sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}" 53 | "SanitizerBlacklist" "SanBlist") 54 | endfunction() 55 | 56 | function(add_sanitizers ...) 57 | # If no sanitizer is enabled, return immediately. 58 | if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR 59 | SANITIZE_UNDEFINED)) 60 | return() 61 | endif () 62 | 63 | foreach (TARGET ${ARGV}) 64 | # Check if this target will be compiled by exactly one compiler. Other- 65 | # wise sanitizers can't be used and a warning should be printed once. 66 | get_target_property(TARGET_TYPE ${TARGET} TYPE) 67 | if (TARGET_TYPE STREQUAL "INTERFACE_LIBRARY") 68 | message(WARNING "Can't use any sanitizers for target ${TARGET}, " 69 | "because it is an interface library and cannot be " 70 | "compiled directly.") 71 | return() 72 | endif () 73 | sanitizer_target_compilers(${TARGET} TARGET_COMPILER) 74 | list(LENGTH TARGET_COMPILER NUM_COMPILERS) 75 | if (NUM_COMPILERS GREATER 1) 76 | message(WARNING "Can't use any sanitizers for target ${TARGET}, " 77 | "because it will be compiled by incompatible compilers. " 78 | "Target will be compiled without sanitizers.") 79 | return() 80 | 81 | # If the target is compiled by no or no known compiler, give a warning. 82 | elseif (NUM_COMPILERS EQUAL 0) 83 | message(WARNING "Sanitizers for target ${TARGET} may not be" 84 | " usable, because it uses no or an unknown compiler. " 85 | "This is a false warning for targets using only " 86 | "object lib(s) as input.") 87 | endif () 88 | 89 | # Add sanitizers for target. 90 | add_sanitize_address(${TARGET}) 91 | add_sanitize_thread(${TARGET}) 92 | add_sanitize_memory(${TARGET}) 93 | add_sanitize_undefined(${TARGET}) 94 | endforeach () 95 | endfunction(add_sanitizers) 96 | 97 | # cmake-format: on 98 | -------------------------------------------------------------------------------- /cmake/FindShellcheck.cmake: -------------------------------------------------------------------------------- 1 | find_program( 2 | SHELLCHECK_EXE 3 | NAMES shellcheck 4 | PATHS /usr/bin/ 5 | PATH_SUFFIXES bin 6 | DOC "shellcheck shell linter" 7 | ) 8 | 9 | include(FindPackageHandleStandardArgs) 10 | 11 | find_package_handle_standard_args(Shellcheck DEFAULT_MSG SHELLCHECK_EXE) 12 | 13 | mark_as_advanced(SHELLCHECK_EXE) 14 | -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | find_program( 2 | SPHINX_EXECUTABLE 3 | NAMES sphinx-build 4 | HINTS $ENV{SPHINX_DIR} 5 | PATH_SUFFIXES bin 6 | DOC "Sphinx documentation generator" 7 | ) 8 | 9 | include(FindPackageHandleStandardArgs) 10 | 11 | find_package_handle_standard_args(Sphinx DEFAULT_MSG SPHINX_EXECUTABLE) 12 | 13 | mark_as_advanced(SPHINX_EXECUTABLE) 14 | -------------------------------------------------------------------------------- /cmake/FindTSan.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 5 | # 2013 Matthew Arsenault 6 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off) 27 | 28 | set(FLAG_CANDIDATES 29 | "-g -fsanitize=thread" 30 | ) 31 | 32 | 33 | # ThreadSanitizer is not compatible with MemorySanitizer. 34 | if (SANITIZE_THREAD AND SANITIZE_MEMORY) 35 | message(FATAL_ERROR "ThreadSanitizer is not compatible with " 36 | "MemorySanitizer.") 37 | endif () 38 | 39 | 40 | include(sanitize-helpers) 41 | 42 | if (SANITIZE_THREAD) 43 | if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND 44 | NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") 45 | message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " 46 | "ThreadSanitizer is supported for Linux systems and macOS only.") 47 | set(SANITIZE_THREAD Off CACHE BOOL 48 | "Enable ThreadSanitizer for sanitized targets." FORCE) 49 | elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) 50 | message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " 51 | "ThreadSanitizer is supported for 64bit systems only.") 52 | set(SANITIZE_THREAD Off CACHE BOOL 53 | "Enable ThreadSanitizer for sanitized targets." FORCE) 54 | else () 55 | sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer" 56 | "TSan") 57 | endif () 58 | endif () 59 | 60 | function (add_sanitize_thread TARGET) 61 | if (NOT SANITIZE_THREAD) 62 | return() 63 | endif () 64 | 65 | sanitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan") 66 | endfunction () 67 | 68 | # cmake-format: on 69 | -------------------------------------------------------------------------------- /cmake/FindUBSan.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 5 | # 2013 Matthew Arsenault 6 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | option(SANITIZE_UNDEFINED 27 | "Enable UndefinedBehaviorSanitizer for sanitized targets." Off) 28 | 29 | set(FLAG_CANDIDATES 30 | "-g -fsanitize=undefined" 31 | ) 32 | 33 | 34 | include(sanitize-helpers) 35 | 36 | if (SANITIZE_UNDEFINED) 37 | sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" 38 | "UndefinedBehaviorSanitizer" "UBSan") 39 | endif () 40 | 41 | function (add_sanitize_undefined TARGET) 42 | if (NOT SANITIZE_UNDEFINED) 43 | return() 44 | endif () 45 | 46 | sanitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan") 47 | endfunction () 48 | 49 | # cmake-format: on 50 | -------------------------------------------------------------------------------- /cmake/Findgcov.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # Findgcov results: 3 | # gcov_FOUND 4 | # gcov_EXECUTABLE 5 | 6 | include(FindPackageHandleStandardArgs) 7 | 8 | # work around CMP0053, see http://public.kitware.com/pipermail/cmake/2014-November/059117.html 9 | set(PROGRAMFILES_x86_ENV "PROGRAMFILES(x86)") 10 | 11 | find_program(gcov_EXECUTABLE 12 | NAMES 13 | gcov 14 | PATHS 15 | "${GCOV_DIR}" 16 | "$ENV{GCOV_DIR}" 17 | "$ENV{PROGRAMFILES}/gcov" 18 | "$ENV{${PROGRAMFILES_x86_ENV}}/gcov" 19 | ) 20 | 21 | find_package_handle_standard_args(gcov 22 | FOUND_VAR 23 | gcov_FOUND 24 | REQUIRED_VARS 25 | gcov_EXECUTABLE 26 | ) 27 | 28 | mark_as_advanced(gcov_EXECUTABLE) 29 | # cmake-format: on 30 | -------------------------------------------------------------------------------- /cmake/Findlcov.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # Findlcov results: 3 | # lcov_FOUND 4 | # lcov_EXECUTABLE 5 | 6 | include(FindPackageHandleStandardArgs) 7 | 8 | # find gcov as it is used by lcov 9 | find_package(gcov) 10 | 11 | # work around CMP0053, see http://public.kitware.com/pipermail/cmake/2014-November/059117.html 12 | set(PROGRAMFILES_x86_ENV "PROGRAMFILES(x86)") 13 | 14 | find_program(lcov_EXECUTABLE 15 | NAMES 16 | lcov 17 | PATHS 18 | "${LCOV_DIR}" 19 | "$ENV{LCOV_DIR}" 20 | "$ENV{PROGRAMFILES}/lcov" 21 | "$ENV{${PROGRAMFILES_x86_ENV}}/lcov" 22 | ) 23 | 24 | find_program(genhtml_EXECUTABLE 25 | NAMES 26 | genhtml 27 | PATHS 28 | "${LCOV_DIR}" 29 | "$ENV{LCOV_DIR}" 30 | "$ENV{PROGRAMFILES}/lcov" 31 | "$ENV{${PROGRAMFILES_x86_ENV}}/lcov" 32 | ) 33 | 34 | find_package_handle_standard_args(lcov 35 | FOUND_VAR 36 | lcov_FOUND 37 | REQUIRED_VARS 38 | lcov_EXECUTABLE 39 | genhtml_EXECUTABLE 40 | ) 41 | 42 | mark_as_advanced( 43 | lcov_EXECUTABLE 44 | genhtml_EXECUTABLE 45 | ) 46 | # cmake-format: on 47 | -------------------------------------------------------------------------------- /cmake/Gcov.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | #mkdir build/coverage 3 | #lcov -d build/source/tests/fiblib-test/CMakeFiles/fiblib-test.dir -c -o build/coverage/fiblib-test.info 4 | #genhtml -o build/coverage/html build/coverage/fiblib-test.info 5 | 6 | set(LCOV_EXCLUDE_COVERAGE) 7 | 8 | # Function to register the target for coverage. 9 | function(generate_lcov_report coverage_target target) 10 | if(NOT TARGET coverage-init) 11 | # Create initialize coverage target. Used for zeroing out counters, etc 12 | add_custom_target( 13 | coverage-zero 14 | COMMAND 15 | ${lcov_EXECUTABLE} 16 | --zerocounters 17 | --base-directory ${CMAKE_BINARY_DIR} 18 | --directory ${CMAKE_SOURCE_DIR} 19 | -q 20 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 21 | ) 22 | 23 | add_custom_target( 24 | coverage-init 25 | COMMAND 26 | ${lcov_EXECUTABLE} 27 | --no-external 28 | --capture 29 | --initial 30 | --base-directory ${CMAKE_BINARY_DIR} 31 | --directory ${CMAKE_SOURCE_DIR} 32 | --output-file ${CMAKE_BINARY_DIR}/coverage-base.info 33 | -q 34 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 35 | BYPRODUCTS ${CMAKE_BINARY_DIR}/coverage-base.info 36 | ) 37 | 38 | add_custom_target( 39 | coverage-info 40 | COMMAND 41 | ${lcov_EXECUTABLE} 42 | --capture 43 | --no-external 44 | --base-directory ${CMAKE_BINARY_DIR} 45 | --directory ${CMAKE_SOURCE_DIR} 46 | --output-file ${CMAKE_BINARY_DIR}/coverage-captured.info 47 | -q 48 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 49 | BYPRODUCTS ${CMAKE_BINARY_DIR}/coverage-captured.info 50 | ) 51 | 52 | add_custom_target( 53 | coverage-merge 54 | COMMAND 55 | ${lcov_EXECUTABLE} 56 | --add-tracefile ${CMAKE_BINARY_DIR}/coverage-base.info 57 | --add-tracefile ${CMAKE_BINARY_DIR}/coverage-captured.info 58 | --output-file ${CMAKE_BINARY_DIR}/coverage-merged.info 59 | -q 60 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 61 | BYPRODUCTS ${CMAKE_BINARY_DIR}/coverage-merged.info 62 | ) 63 | 64 | add_custom_target( 65 | coverage-filter 66 | COMMAND 67 | ${lcov_EXECUTABLE} 68 | --base-directory ${CMAKE_BINARY_DIR} 69 | --directory ${CMAKE_SOURCE_DIR} 70 | --remove ${CMAKE_BINARY_DIR}/coverage-merged.info 71 | ${LCOV_EXCLUDE_COVERAGE} 72 | --output-file ${CMAKE_BINARY_DIR}/coverage-filtered.info 73 | -q 74 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 75 | BYPRODUCTS ${CMAKE_BINARY_DIR}/coverage-filtered.info 76 | ) 77 | 78 | add_custom_target( 79 | coverage-report 80 | COMMAND 81 | ${genhtml_EXECUTABLE} 82 | --output-directory ${CMAKE_BINARY_DIR}/coverage 83 | --title "${PROJECT_NAME} Test Coverage" 84 | --num-spaces 4 85 | ${CMAKE_BINARY_DIR}/coverage-filtered.info 86 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 87 | DEPENDS ${CMAKE_BINARY_DIR}/coverage-filtered.info 88 | ) 89 | 90 | add_dependencies(coverage-init coverage-zero) 91 | add_dependencies(coverage-info coverage-init) 92 | add_dependencies(coverage-merge coverage-info) 93 | add_dependencies(coverage-filter coverage-merge) 94 | add_dependencies(coverage-report coverage-filter) 95 | add_dependencies(coverage coverage-report) 96 | endif() 97 | 98 | add_custom_target(${coverage_target} 99 | COMMAND $ 100 | ) 101 | 102 | add_dependencies(coverage-info ${coverage_target}) 103 | add_dependencies(${coverage_target} coverage-init) 104 | 105 | endfunction() 106 | # cmake-format: on 107 | -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # - Returns a version string from Git 3 | # 4 | # These functions force a re-configure on each git commit so that you can 5 | # trust the values of the variables in your build system. 6 | # 7 | # get_git_head_revision( [ ...]) 8 | # 9 | # Returns the refspec and sha hash of the current head revision 10 | # 11 | # git_describe( [ ...]) 12 | # 13 | # Returns the results of git describe on the source tree, and adjusting 14 | # the output so that it tests false if an error occurs. 15 | # 16 | # git_get_exact_tag( [ ...]) 17 | # 18 | # Returns the results of git describe --exact-match on the source tree, 19 | # and adjusting the output so that it tests false if there was no exact 20 | # matching tag. 21 | # 22 | # Requires CMake 2.6 or newer (uses the 'function' command) 23 | # 24 | # Original Author: 25 | # 2009-2010 Ryan Pavlik 26 | # http://academic.cleardefinition.com 27 | # Iowa State University HCI Graduate Program/VRAC 28 | # 29 | # Copyright Iowa State University 2009-2010. 30 | # Distributed under the Boost Software License, Version 1.0. 31 | # (See accompanying file LICENSE_1_0.txt or copy at 32 | # http://www.boost.org/LICENSE_1_0.txt) 33 | 34 | if(__get_git_revision_description) 35 | return() 36 | endif() 37 | set(__get_git_revision_description YES) 38 | 39 | # We must run the following at "include" time, not at function call time, 40 | # to find the path to this module rather than the path to a calling list file 41 | get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) 42 | 43 | function(get_git_head_revision _refspecvar _hashvar) 44 | set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 45 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 46 | while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories 47 | set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") 48 | get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) 49 | if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) 50 | # We have reached the root directory, we are not in git 51 | set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 52 | set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 53 | return() 54 | endif() 55 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 56 | endwhile() 57 | # check if this is a submodule 58 | if(NOT IS_DIRECTORY ${GIT_DIR}) 59 | file(READ ${GIT_DIR} submodule) 60 | string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) 61 | get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) 62 | get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) 63 | endif() 64 | set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") 65 | if(NOT EXISTS "${GIT_DATA}") 66 | file(MAKE_DIRECTORY "${GIT_DATA}") 67 | endif() 68 | 69 | if(NOT EXISTS "${GIT_DIR}/HEAD") 70 | return() 71 | endif() 72 | set(HEAD_FILE "${GIT_DATA}/HEAD") 73 | configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) 74 | 75 | configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" 76 | "${GIT_DATA}/grabRef.cmake" 77 | @ONLY) 78 | include("${GIT_DATA}/grabRef.cmake") 79 | 80 | set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) 81 | set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) 82 | endfunction() 83 | 84 | function(git_describe _var) 85 | if(NOT GIT_FOUND) 86 | find_package(Git QUIET) 87 | endif() 88 | get_git_head_revision(refspec hash) 89 | if(NOT GIT_FOUND) 90 | set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) 91 | return() 92 | endif() 93 | if(NOT hash) 94 | set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) 95 | return() 96 | endif() 97 | 98 | # TODO sanitize 99 | #if((${ARGN}" MATCHES "&&") OR 100 | # (ARGN MATCHES "||") OR 101 | # (ARGN MATCHES "\\;")) 102 | # message("Please report the following error to the project!") 103 | # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") 104 | #endif() 105 | 106 | #message(STATUS "Arguments to execute_process: ${ARGN}") 107 | 108 | execute_process(COMMAND 109 | "${GIT_EXECUTABLE}" 110 | describe 111 | ${hash} 112 | ${ARGN} 113 | WORKING_DIRECTORY 114 | "${CMAKE_CURRENT_SOURCE_DIR}" 115 | RESULT_VARIABLE 116 | res 117 | OUTPUT_VARIABLE 118 | out 119 | ERROR_QUIET 120 | OUTPUT_STRIP_TRAILING_WHITESPACE) 121 | if(NOT res EQUAL 0) 122 | set(out "${out}-${res}-NOTFOUND") 123 | endif() 124 | 125 | set(${_var} "${out}" PARENT_SCOPE) 126 | endfunction() 127 | 128 | function(git_get_exact_tag _var) 129 | git_describe(out --exact-match ${ARGN}) 130 | set(${_var} "${out}" PARENT_SCOPE) 131 | endfunction() 132 | 133 | # cmake-format: on 134 | -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Internal file for GetGitRevisionDescription.cmake 3 | # 4 | # Requires CMake 2.6 or newer (uses the 'function' command) 5 | # 6 | # Original Author: 7 | # 2009-2010 Ryan Pavlik 8 | # http://academic.cleardefinition.com 9 | # Iowa State University HCI Graduate Program/VRAC 10 | # 11 | # Copyright Iowa State University 2009-2010. 12 | # Distributed under the Boost Software License, Version 1.0. 13 | # (See accompanying file LICENSE_1_0.txt or copy at 14 | # http://www.boost.org/LICENSE_1_0.txt) 15 | 16 | set(HEAD_HASH) 17 | set(HEAD_REF) 18 | 19 | if (NOT EXISTS "@HEAD_FILE@") 20 | return() 21 | endif() 22 | 23 | file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) 24 | 25 | string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) 26 | if(HEAD_CONTENTS MATCHES "ref") 27 | # named branch 28 | string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") 29 | if(EXISTS "@GIT_DIR@/${HEAD_REF}") 30 | configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 31 | elseif(EXISTS "@GIT_DIR@/packed-refs") 32 | configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) 33 | file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) 34 | if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") 35 | set(HEAD_HASH "${CMAKE_MATCH_1}") 36 | endif() 37 | endif() 38 | else() 39 | # detached HEAD 40 | configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) 41 | endif() 42 | 43 | if(NOT HEAD_HASH AND EXISTS "@GIT_DATA@/head-ref") 44 | file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) 45 | string(STRIP "${HEAD_HASH}" HEAD_HASH) 46 | endif() 47 | -------------------------------------------------------------------------------- /cmake/MiscFunctions.cmake: -------------------------------------------------------------------------------- 1 | # Set the default build type if not set by the user. 2 | function(set_default_build_type buildType) 3 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 4 | message( 5 | STATUS "Setting build type to '${buildType}' as none was specified." 6 | ) 7 | set(CMAKE_BUILD_TYPE 8 | "${buildType}" 9 | CACHE STRING "Choose the type of build." FORCE 10 | ) 11 | # Set the possible values of build type for cmake-gui 12 | set_property( 13 | CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" 14 | "RelWithDebInfo" 15 | ) 16 | endif() 17 | endfunction() 18 | 19 | function(check_no_in_source_build) 20 | if(CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR) 21 | message( 22 | WARNING 23 | "Building in-source is not recommended! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt" 24 | ) 25 | endif() 26 | endfunction() 27 | -------------------------------------------------------------------------------- /cmake/README.md: -------------------------------------------------------------------------------- 1 | # CMake Modules 2 | 3 | List of all modules and third party licenses that are used in this project. 4 | 5 | `warnings.cmake` 6 | `FindShellcheck.cmake` 7 | `FindCMakeFormat.cmake` 8 | `FindSphinx.cmake` 9 | `FindCppCheck.cmake` 10 | - custom implementation (cpp-starter-project) 11 | 12 | `FindASan.cmake` 13 | `FindMSan.cmake` 14 | `FindSanitizers.cmake` 15 | `FindTSan.cmake` 16 | `FindUBSan.cmake` 17 | `sanitize-helpers.cmake` 18 | - Website: https://github.com/arsenm/sanitizers-cmake (2018-12-15) 19 | - License: [MIT License](https://github.com/arsenm/sanitizers-cmake/blob/master/LICENSE) 20 | 21 | `GetGitRevisionDescription.cmake` 22 | `GetGitRevisionDescription.cmake.in` 23 | - Taken from rpavlik/cmake-modules 24 | - Website: https://github.com/rpavlik/cmake-modules (2019-01-29) 25 | - License: [Boost Software License, Version 1.0](https://github.com/rpavlik/cmake-modules/blob/master/LICENSE_1_0.txt) 26 | 27 | `coverage.cmake` 28 | `Gcov.cmake` 29 | `Findgcov.cmake` 30 | `Findlcov.cmake` 31 | - Website: https://github.com/cginternals/cmake-init 32 | - License: [MIT License](https://github.com/cginternals/cmake-init/blob/master/LICENSE) 33 | 34 | 35 | `Catch.cmake` 36 | `CatchAddTests.cmake` 37 | - Website: https://github.com/catchorg/Catch2 (2019-01-30) 38 | - License: [Boost Software License 1.0](https://github.com/catchorg/Catch2/blob/master/LICENSE.txt) 39 | -------------------------------------------------------------------------------- /cmake/asan-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 6 | # 2013 Matthew Arsenault 7 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | # This script is a wrapper for AddressSanitizer. In some special cases you need 28 | # to preload AddressSanitizer to avoid error messages - e.g. if you're 29 | # preloading another library to your application. At the moment this script will 30 | # only do something, if we're running on a Linux platform. OSX might not be 31 | # affected. 32 | 33 | 34 | # Exit immediately, if platform is not Linux. 35 | if [ "$(uname)" != "Linux" ] 36 | then 37 | exec $@ 38 | fi 39 | 40 | 41 | # Get the used libasan of the application ($1). If a libasan was found, it will 42 | # be prepended to LD_PRELOAD. 43 | libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1) 44 | if [ -n "$libasan" ] 45 | then 46 | if [ -n "$LD_PRELOAD" ] 47 | then 48 | export LD_PRELOAD="$libasan:$LD_PRELOAD" 49 | else 50 | export LD_PRELOAD="$libasan" 51 | fi 52 | fi 53 | 54 | # Execute the application. 55 | exec $@ 56 | -------------------------------------------------------------------------------- /cmake/clang-tidy.cmake: -------------------------------------------------------------------------------- 1 | if(ENABLE_CLANG_TIDY) 2 | message(STATUS "clang-tidy enabled") 3 | set(CMAKE_CXX_CLANG_TIDY clang-tidy -format-style=file) 4 | 5 | elseif(ENABLE_CLANG_TIDY_FIX) 6 | message(STATUS "clang-tidy with auto-fix enabled") 7 | set(CMAKE_CXX_CLANG_TIDY clang-tidy -format-style=file -fix) 8 | 9 | endif() 10 | -------------------------------------------------------------------------------- /cmake/coverage.cmake: -------------------------------------------------------------------------------- 1 | # Original from https://github.com/cginternals/cmake- 2 | # init/blob/master/cmake/Coverage.cmake Modified 3 | include(${CMAKE_CURRENT_LIST_DIR}/Gcov.cmake) 4 | 5 | set(COVERAGE_ENABLED OFF BOOL) 6 | 7 | set(LCOV_EXCLUDE_COVERAGE 8 | ${LCOV_EXCLUDE_COVERAGE} "\"*/googletest/*\"" "\"*v1*\"" "\"/usr/*\"" 9 | "\"*/external/*\"" "\"*/third_party/*\"" 10 | ) 11 | 12 | # Function to register a target for enabled coverage report. Use this function 13 | # on test executables. 14 | function(generate_coverage_report target) 15 | if(${COVERAGE_ENABLED}) 16 | if(NOT TARGET coverage) 17 | add_custom_target(coverage) 18 | 19 | set_target_properties( 20 | coverage PROPERTIES FOLDER "Maintenance" EXCLUDE_FROM_DEFAULT_BUILD 1 21 | ) 22 | endif() 23 | generate_lcov_report(coverage-${target} ${target} ${ARGN}) 24 | add_dependencies(coverage coverage-${target}) 25 | endif() 26 | endfunction() 27 | 28 | # Enable or disable coverage. Sets COVERAGE_ENABLED which is used by 29 | # target_enable_coverage 30 | function(activate_coverage status) 31 | if(NOT ${status}) 32 | set(COVERAGE_ENABLED 33 | ${status} 34 | PARENT_SCOPE 35 | ) 36 | message(STATUS "Coverage lcov skipped: Manually disabled") 37 | return() 38 | endif() 39 | 40 | find_package(lcov) 41 | 42 | if(NOT lcov_FOUND) 43 | set(COVERAGE_ENABLED 44 | OFF 45 | PARENT_SCOPE 46 | ) 47 | message(STATUS "Coverage lcov skipped: lcov not found") 48 | return() 49 | endif() 50 | 51 | set(COVERAGE_ENABLED 52 | ${status} 53 | PARENT_SCOPE 54 | ) 55 | message(STATUS "Coverage report enabled") 56 | endfunction() 57 | 58 | # Add compile/linker flags to the target for code coverage. Requires 59 | # COVERAGE_ENABLED to be ON. 60 | function(target_enable_coverage target) 61 | if(NOT TARGET ${target}) 62 | message(WARNING "target_enable_coverage: ${target} is not a target.") 63 | return() 64 | endif() 65 | 66 | if(${COVERAGE_ENABLED}) 67 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" 68 | STREQUAL "Clang" 69 | ) 70 | target_compile_options(${target} PRIVATE -g --coverage) 71 | endif() 72 | 73 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_SYSTEM_NAME}" 74 | STREQUAL "Linux" 75 | ) 76 | target_link_libraries(${target} PRIVATE -g --coverage) 77 | endif() 78 | 79 | endif() 80 | endfunction() 81 | -------------------------------------------------------------------------------- /cmake/sanitize-helpers.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 5 | # 2013 Matthew Arsenault 6 | # 2015-2016 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | # Helper function to get the language of a source file. 27 | function (sanitizer_lang_of_source FILE RETURN_VAR) 28 | get_filename_component(LONGEST_EXT "${FILE}" EXT) 29 | # If extension is empty return. This can happen for extensionless headers 30 | if("${LONGEST_EXT}" STREQUAL "") 31 | set(${RETURN_VAR} "" PARENT_SCOPE) 32 | return() 33 | endif() 34 | # Get shortest extension as some files can have dot in their names 35 | string(REGEX REPLACE "^.*(\\.[^.]+)$" "\\1" FILE_EXT ${LONGEST_EXT}) 36 | string(TOLOWER "${FILE_EXT}" FILE_EXT) 37 | string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) 38 | 39 | get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 40 | foreach (LANG ${ENABLED_LANGUAGES}) 41 | list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) 42 | if (NOT ${TEMP} EQUAL -1) 43 | set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) 44 | return() 45 | endif () 46 | endforeach() 47 | 48 | set(${RETURN_VAR} "" PARENT_SCOPE) 49 | endfunction () 50 | 51 | 52 | # Helper function to get compilers used by a target. 53 | function (sanitizer_target_compilers TARGET RETURN_VAR) 54 | # Check if all sources for target use the same compiler. If a target uses 55 | # e.g. C and Fortran mixed and uses different compilers (e.g. clang and 56 | # gfortran) this can trigger huge problems, because different compilers may 57 | # use different implementations for sanitizers. 58 | set(BUFFER "") 59 | get_target_property(TSOURCES ${TARGET} SOURCES) 60 | foreach (FILE ${TSOURCES}) 61 | # If expression was found, FILE is a generator-expression for an object 62 | # library. Object libraries will be ignored. 63 | string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) 64 | if ("${_file}" STREQUAL "") 65 | sanitizer_lang_of_source(${FILE} LANG) 66 | if (LANG) 67 | list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID}) 68 | endif () 69 | endif () 70 | endforeach () 71 | 72 | list(REMOVE_DUPLICATES BUFFER) 73 | set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE) 74 | endfunction () 75 | 76 | 77 | # Helper function to check compiler flags for language compiler. 78 | function (sanitizer_check_compiler_flag FLAG LANG VARIABLE) 79 | if (${LANG} STREQUAL "C") 80 | include(CheckCCompilerFlag) 81 | check_c_compiler_flag("${FLAG}" ${VARIABLE}) 82 | 83 | elseif (${LANG} STREQUAL "CXX") 84 | include(CheckCXXCompilerFlag) 85 | check_cxx_compiler_flag("${FLAG}" ${VARIABLE}) 86 | 87 | elseif (${LANG} STREQUAL "Fortran") 88 | # CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible 89 | # with older Cmake versions, we will check if this module is present 90 | # before we use it. Otherwise we will define Fortran coverage support as 91 | # not available. 92 | include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED) 93 | if (INCLUDED) 94 | check_fortran_compiler_flag("${FLAG}" ${VARIABLE}) 95 | elseif (NOT CMAKE_REQUIRED_QUIET) 96 | message(STATUS "Performing Test ${VARIABLE}") 97 | message(STATUS "Performing Test ${VARIABLE}" 98 | " - Failed (Check not supported)") 99 | endif () 100 | endif() 101 | endfunction () 102 | 103 | 104 | # Helper function to test compiler flags. 105 | function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX) 106 | set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY}) 107 | 108 | get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 109 | foreach (LANG ${ENABLED_LANGUAGES}) 110 | # Sanitizer flags are not dependend on language, but the used compiler. 111 | # So instead of searching flags foreach language, search flags foreach 112 | # compiler used. 113 | set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) 114 | if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS) 115 | foreach (FLAG ${FLAG_CANDIDATES}) 116 | if(NOT CMAKE_REQUIRED_QUIET) 117 | message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]") 118 | endif() 119 | 120 | set(CMAKE_REQUIRED_FLAGS "${FLAG}") 121 | unset(${PREFIX}_FLAG_DETECTED CACHE) 122 | sanitizer_check_compiler_flag("${FLAG}" ${LANG} 123 | ${PREFIX}_FLAG_DETECTED) 124 | 125 | if (${PREFIX}_FLAG_DETECTED) 126 | # If compiler is a GNU compiler, search for static flag, if 127 | # SANITIZE_LINK_STATIC is enabled. 128 | if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU")) 129 | string(TOLOWER ${PREFIX} PREFIX_lower) 130 | sanitizer_check_compiler_flag( 131 | "-static-lib${PREFIX_lower}" ${LANG} 132 | ${PREFIX}_STATIC_FLAG_DETECTED) 133 | 134 | if (${PREFIX}_STATIC_FLAG_DETECTED) 135 | set(FLAG "-static-lib${PREFIX_lower} ${FLAG}") 136 | endif () 137 | endif () 138 | 139 | set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING 140 | "${NAME} flags for ${COMPILER} compiler.") 141 | mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) 142 | break() 143 | endif () 144 | endforeach () 145 | 146 | if (NOT ${PREFIX}_FLAG_DETECTED) 147 | set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING 148 | "${NAME} flags for ${COMPILER} compiler.") 149 | mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) 150 | 151 | message(WARNING "${NAME} is not available for ${COMPILER} " 152 | "compiler. Targets using this compiler will be " 153 | "compiled without ${NAME}.") 154 | endif () 155 | endif () 156 | endforeach () 157 | endfunction () 158 | 159 | 160 | # Helper to assign sanitizer flags for TARGET. 161 | function (sanitizer_add_flags TARGET NAME PREFIX) 162 | # Get list of compilers used by target and check, if sanitizer is available 163 | # for this target. Other compiler checks like check for conflicting 164 | # compilers will be done in add_sanitizers function. 165 | sanitizer_target_compilers(${TARGET} TARGET_COMPILER) 166 | list(LENGTH TARGET_COMPILER NUM_COMPILERS) 167 | if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "") 168 | return() 169 | endif() 170 | 171 | # Set compile- and link-flags for target. 172 | set_property(TARGET ${TARGET} APPEND_STRING 173 | PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") 174 | set_property(TARGET ${TARGET} APPEND_STRING 175 | PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}") 176 | set_property(TARGET ${TARGET} APPEND_STRING 177 | PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") 178 | endfunction () 179 | 180 | # cmake-format: on 181 | -------------------------------------------------------------------------------- /cmake/warnings.cmake: -------------------------------------------------------------------------------- 1 | function(enable_warnings warning_target) 2 | if(NOT TARGET ${warning_target}) 3 | message(WARNING "cpp-starter warnings: ${warning_target} is not a target.") 4 | return() 5 | endif() 6 | 7 | message( 8 | STATUS 9 | "Enable ${CMAKE_CXX_COMPILER_ID} compiler warnings for target ${warning_target}" 10 | ) 11 | 12 | if(MSVC) 13 | target_compile_options(${warning_target} PRIVATE /W3) 14 | 15 | elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 16 | target_compile_options( 17 | ${warning_target} 18 | PRIVATE -Wall 19 | -Wextra 20 | -pedantic 21 | # Warnings that are not enabled but -Wall/-Wextra See 22 | # https://kristerw.blogspot.com/2017/09/useful-gcc-warning- 23 | # options-not-enabled.html 24 | -Wunknown-pragmas 25 | -Wundef 26 | -Wold-style-cast # warn for c-style casts (e.g. `(int) 3.0`) 27 | -Wuseless-cast 28 | -Wdisabled-optimization 29 | -Wstrict-overflow=4 30 | -Winit-self 31 | -Wpointer-arith 32 | -Wduplicated-cond 33 | -Wdouble-promotion 34 | -Wshadow # warn the user if a variable declaration shadows one 35 | # from a parent context 36 | -Wduplicated-branches 37 | -Wrestrict 38 | -Wnull-dereference # warn if a null dereference is detected 39 | -Wlogical-op 40 | -Wunsafe-loop-optimizations 41 | -Wno-error=unsafe-loop-optimizations 42 | -Wformat=2 43 | -Wmissing-field-initializers 44 | ) 45 | 46 | elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" 47 | OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" 48 | ) 49 | target_compile_options( 50 | ${warning_target} 51 | PRIVATE -Wall -Wextra -pedantic -Wdocumentation # Warns about doxygen 52 | # variable name 53 | # mismatches, etc. 54 | ) 55 | 56 | else() 57 | message( 58 | WARNING 59 | "Unsupported compiler '${CMAKE_CXX_COMPILER_ID}'. It may not work." 60 | ) 61 | target_compile_options(${warning_target} PRIVATE -Wall -Wextra -pedantic) 62 | 63 | endif() 64 | endfunction() 65 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: nearest 4 | range: "60...90" 5 | status: 6 | project: 7 | default: 8 | threshold: 2% 9 | patch: 10 | default: 11 | target: 80% 12 | ignore: 13 | - "**/external/clara/clara.hpp" 14 | 15 | codecov: 16 | branch: main 17 | 18 | comment: 19 | layout: "diff" 20 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | catch2/2.13.7 3 | range-v3/0.11.0 4 | 5 | [generators] 6 | cmake 7 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- 1 | `.gitkeep` files only exists for git to be able to track empty folders. 2 | It isn't handled in any special way like `.gitignore` files. 3 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Doxygen 3 | find_package(Doxygen OPTIONAL_COMPONENTS dot) 4 | if(DOXYGEN_FOUND) 5 | set(DOXYGEN_PROJECT_BRIEF "${PROJECT_NAME}") 6 | set(DOXYGEN_GENERATE_TREEVIEW YES) 7 | set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ../README.md) 8 | 9 | doxygen_add_docs( 10 | doxygen ../README.md ../src COMMENT "Generate doxygen documentation" 11 | ) 12 | endif() 13 | 14 | # ------------------------------------------------------------------------------ 15 | # Sphinx User Documentation 16 | find_package(Sphinx) 17 | if(SPHINX_FOUND) 18 | add_custom_target( 19 | docs 20 | COMMAND 21 | ${SPHINX_EXECUTABLE} -M html "${CMAKE_CURRENT_SOURCE_DIR}/user/source" 22 | "${CMAKE_CURRENT_BINARY_DIR}/user" 23 | COMMENT "Build HTML documentation with Sphinx" 24 | ) 25 | endif() 26 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | The `docs/` directory is designated to contain project documentation. 4 | The documentation process, tools, and layout is not prescribed by this 5 | document. 6 | 7 | ## Build Documentation 8 | 9 | User documentation can be found in `./user` and is created using Sphinx. 10 | This project does not contain a `Doxyfile` as we have a custom CMake 11 | target for this. 12 | 13 | You need to have Sphinx and the 14 | [Read the Docs Sphinx Theme](https://github.com/rtfd/sphinx_rtd_theme) 15 | installed. 16 | 17 | ```sh 18 | # Create user documentation using the Makefile 19 | cd docs/user 20 | make html 21 | 22 | # Create Doxygen and Sphinx documentationUsing CMake 23 | mkdir build && cd $_ 24 | cmake .. 25 | make doxygen 26 | make docs 27 | ``` 28 | 29 | ## Publish User Documentation 30 | On Linux run: `./user/publish_gh-pages.sh`. 31 | You can find our documentation on: https://bugwelle.github.io/cpp-starter-project/ 32 | -------------------------------------------------------------------------------- /docs/cpp-starter/README.md: -------------------------------------------------------------------------------- 1 | # cpp-starter-project Documentation 2 | 3 | - [README](README.md) 4 | - [C++ Project Structure](project-structure.md) 5 | - [Build System: CMake](cmake.md) 6 | - [Continuous Integration](continuous-integration.md) 7 | - [Continuous Deployment](continuous-deployment.md) 8 | - [Testing with Catch2](testing.md) 9 | - [Static Code Analysis](linters.md) 10 | - [HowTo: Documentation](documentation.md) 11 | - [HowTo: GitHub Pages](github-pages.md) 12 | -------------------------------------------------------------------------------- /docs/cpp-starter/cmake.md: -------------------------------------------------------------------------------- 1 | # CMake 2 | CMake is a build system generator (it's not a build system per definition). 3 | We use modern CMake which refers in our case to CMake 3.12 and above. 4 | 5 | ## What is "modern"? 6 | DO NOT use `link_libraries()` or `include_directories()` without a `target_` 7 | prefix. Modern CMake is target based. 8 | 9 | These talks give a good introduction into modern CMake: 10 | 11 | - [CppCon 2017: Mathieu Ropert “Using Modern CMake Patterns to Enforce a Good Modular Design”](https://www.youtube.com/watch?v=eC9-iRN2b04) 12 | - [C++Now 2017: Daniel Pfeifer “Effective CMake" ](https://www.youtube.com/watch?v=bsXLMQ6WgIk) 13 | 14 | I beg of you: Don't follow tutorials that only require CMake >= 2.8. You'll 15 | only learn outdated CMake and it will **not** be fun. You will hate CMake. 16 | Target based CMake is way easier. :-) 17 | 18 | If you find projects that manage their sources using a global `PROJECT_SRC` 19 | variable, it's outdated! 20 | -------------------------------------------------------------------------------- /docs/cpp-starter/continuous-deployment.md: -------------------------------------------------------------------------------- 1 | # Continuous Deployment 2 | -------------------------------------------------------------------------------- /docs/cpp-starter/continuous-integration.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration 2 | No open-source project should start without using at least one continuous 3 | integration service. It makes handling pull requests so much easier. 4 | This projects tries to use as many CI services as possible. 5 | We use: 6 | 7 | - [Travis CI](https://travis-ci.org/) 8 | - [AppVeyor](https://appveyor.com/) 9 | - [GitLab CI](https://gitlab.com/) 10 | 11 | ## Travis CI 12 | Travis CI is free for open source projects. We use it for linux and macOS 13 | builds. Because it's free, please refrain from creating *many* jobs that 14 | take hours to complete. We use Travis CI for linux and macOS builds. 15 | 16 | ## AppVeyor 17 | AppVeyor is free for oppen source projects. This project uses it for Windows 18 | builds only. 19 | 20 | ## GitLab CI 21 | GitLab CI is also available for GitHub projects. See its 22 | [documentation](https://about.gitlab.com/solutions/github/) for set-up 23 | instructions. 24 | 25 | We use a custom docker image that is also hosted on GitLab. You can find it 26 | at https://gitlab.com/bugwelle/docker-modern-cpp-cmake 27 | It includes recent versions of GCC and clang, cppcheck and other usefule tools. 28 | By using custom docker images we could also use the latest C++ features 29 | available, etc. 30 | -------------------------------------------------------------------------------- /docs/cpp-starter/documentation.md: -------------------------------------------------------------------------------- 1 | # HowTo: Documentation 2 | I find it difficult to create good documentation. We begin with some basics: 3 | 4 | - user documentation 5 | - developer documentation 6 | 7 | If you develop an end-user executable then documentation for them is a must- 8 | have. Developers on the other hand want to read your code, want to improve 9 | stuff. Personally I like doxygen as it is more or less standard. 10 | 11 | 12 | ## User Documentation 13 | Sphinx is a tool that makes it easy to create beautiful documentation. 14 | We use it in combination with the 15 | [Read The Docs Sphinx Theme](https://sphinx-rtd-theme.readthedocs.io/en/latest/). 16 | 17 | 18 | ## Developer Documentation 19 | This project uses [doxygen](http://doxygen.nl/). Many projects include a 20 | `Doxyfile` that is used by the `doxygen` command-line tool. We instead 21 | use some CMake built-ins. Have a look at [CMakeLists.txt](./CMakeLists.txt). 22 | You can create this project's doxygen documentation using `make docs`. 23 | -------------------------------------------------------------------------------- /docs/cpp-starter/github-pages.md: -------------------------------------------------------------------------------- 1 | # HowTo: GitHub Pages 2 | It's actually quite simple to publish your sphinx documentation on GitHub. 3 | I assume that you're on a linux distribution. 4 | 5 | 1. create a *clean* new branch without any source code: 6 | `git checkout --orphan gh-pages` 7 | 2. create an empty `.nojekyll` file (it tells GitHub to *not* use Jekyll), 8 | stage and commit it 9 | 3. push the new and empty branch git GitHub 10 | 4. run `./docs/user/publish_gh-pages.sh` which: 11 | - *resets* your branch to upstream (!) 12 | - copies the CHANGELOG and converts it to reStructuredText 13 | - creates URLs to GitHub issues (simple pattern matching `#nnn`) 14 | - creates sphinx documentation 15 | - copies the generated documentation to `gh-pages` 16 | - creates a new commit 17 | - pushes changes to GitHub 18 | 5. Enable GitHub pages in your project's GitHub settings 19 | 20 | All in one script: 21 | 22 | ```sh 23 | cd project-root 24 | git checkout --orphan gh-pages 25 | git rm --cached -r . # Clear working directory 26 | touch .nojekyll 27 | git add .nojekyll 28 | git commit -m "init gh-pages" 29 | git push origin gh-pages 30 | git checkout main 31 | ./docs/user/publish_gh-pages.sh 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/cpp-starter/linters.md: -------------------------------------------------------------------------------- 1 | # Static Code Analysis Tools 2 | 3 | ## `cppcheck` 4 | TODO 5 | 6 | ## `clang-tidy` 7 | TODO 8 | 9 | ## `shellcheck` 10 | [shellcheck](https://github.com/koalaman/shellcheck) is used to check bash 11 | files for common bugs like missing quotes around paths, etc. 12 | We use shellcheck because we some bash files for our CI services. 13 | -------------------------------------------------------------------------------- /docs/cpp-starter/project-structure.md: -------------------------------------------------------------------------------- 1 | # Project Structure 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/cpp-starter/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/user/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/user/create_changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | cd "$( cd "$(dirname "$0")"; pwd -P )" 7 | 8 | NOTES=source/release-notes.rst 9 | 10 | cat ./release_notes_header.rst > $NOTES 11 | 12 | cat ../../CHANGELOG.md >> $NOTES 13 | 14 | echo "Markdown --> reStructuredText" 15 | 16 | # We assume that the longest title is ~30 characters and we don't 17 | # have headers nested deeper than level 4 (

) 18 | sed -i.bak '/####/ a ------------------------------------' $NOTES 19 | sed -i.bak 's/#### //' $NOTES 20 | 21 | sed -i.bak '/###/ a ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' $NOTES 22 | sed -i.bak 's/### //' $NOTES 23 | 24 | sed -i.bak '/##/ a ************************************' $NOTES 25 | sed -i.bak 's/## //' $NOTES 26 | 27 | sed -i.bak 's/`/``/g' $NOTES 28 | sed -i.bak '/# Changelog/d' $NOTES 29 | 30 | # Create issue/pr URLs 31 | sed -i.bak -r 's/#([0-9]+)/`#\1 `_/g' $NOTES 32 | 33 | rm -f $NOTES.bak 34 | 35 | echo "Done." 36 | -------------------------------------------------------------------------------- /docs/user/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/user/publish_gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | # cd into this file's directory 7 | cd "$( cd "$(dirname "$0")"; pwd -P )" 8 | 9 | # get a clean main branch 10 | git checkout main 11 | git pull origin main 12 | git clean -df 13 | git checkout -- . 14 | git fetch --all 15 | 16 | # build html docs from sphinx files 17 | ./create_changelog.sh 18 | sphinx-build -M html source build 19 | 20 | # create or use orphaned gh-pages branch 21 | branch_name=gh-pages 22 | if [ "$(git branch --list "$branch_name")" ]; then 23 | git stash 24 | git checkout $branch_name 25 | git pull origin $branch_name 26 | git checkout stash -- . 2> /dev/null || echo "Nothing on stash stack" # force git stash to overwrite added files 27 | 28 | else 29 | echo "You need to create a branch '$branch_name' first." 30 | exit 1 31 | fi 32 | 33 | if [ -d "build" ]; then 34 | (ls | grep -v "build" | xargs rm -r) || echo "Nothing to clean" 35 | cd ../.. # is the root directory og GitHub Pages 36 | mv docs/user/build/html/* . && rm -rf "docs" 37 | git add . 38 | git commit -m "new pages version $(date)" 39 | git push origin gh-pages 40 | # github.com recognizes gh-pages branch and create pages 41 | # url scheme https//:[github-handle].github.io/[repository] 42 | else 43 | echo "directory build does not exists" 44 | fi 45 | 46 | git checkout main 47 | -------------------------------------------------------------------------------- /docs/user/release_notes_header.rst: -------------------------------------------------------------------------------- 1 | .. |br| raw:: html 2 | 3 |
4 | 5 | ============= 6 | Release Notes 7 | ============= 8 | 9 | .. note:: 10 | 11 | This information is drawn from the GitHub cpp-starter-project changelog: |br| 12 | https://github.com/bugwelle/cpp-starter-project/blob/master/CHANGELOG.md 13 | 14 | -------------------------------------------------------------------------------- /docs/user/source/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugwelle/cpp-starter-project/807d5e80decf3aacab1c0642a908755b80bb15c0/docs/user/source/_static/.gitkeep -------------------------------------------------------------------------------- /docs/user/source/about.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | About 3 | ===== 4 | 5 | cpp-starter-project is an entry point for new projects. 6 | -------------------------------------------------------------------------------- /docs/user/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'cpp-starter-project' 23 | copyright = '2018, cpp-starter-project authors' 24 | author = 'cpp-starter-project authors' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.mathjax', 43 | 'sphinx.ext.githubpages', 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | # source_suffix = ['.rst', '.md'] 53 | source_suffix = '.rst' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | # 61 | # This is also used if you do content translation via gettext catalogs. 62 | # Usually you set "language" from the command line for these cases. 63 | language = None 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | # This pattern also affects html_static_path and html_extra_path. 68 | exclude_patterns = [] 69 | 70 | # The name of the Pygments (syntax highlighting) style to use. 71 | pygments_style = None 72 | 73 | 74 | # -- Options for HTML output ------------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. See the documentation for 77 | # a list of builtin themes. 78 | # 79 | html_theme = 'sphinx_rtd_theme' 80 | 81 | # Theme options are theme-specific and customize the look and feel of a theme 82 | # further. For a list of options available for each theme, see the 83 | # documentation. 84 | # 85 | # html_theme_options = {} 86 | 87 | # Add any paths that contain custom static files (such as style sheets) here, 88 | # relative to this directory. They are copied after the builtin static files, 89 | # so a file named "default.css" will overwrite the builtin "default.css". 90 | html_static_path = ['_static'] 91 | 92 | # Custom sidebar templates, must be a dictionary that maps document names 93 | # to template names. 94 | # 95 | # The default sidebars (for documents that don't match any pattern) are 96 | # defined by theme itself. Builtin themes are using these templates by 97 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 98 | # 'searchbox.html']``. 99 | # 100 | # html_sidebars = {} 101 | 102 | 103 | # -- Options for HTMLHelp output --------------------------------------------- 104 | 105 | # Output file base name for HTML help builder. 106 | htmlhelp_basename = 'cpp-starter-projectdoc' 107 | 108 | 109 | # -- Options for LaTeX output ------------------------------------------------ 110 | 111 | latex_elements = { 112 | # The paper size ('letterpaper' or 'a4paper'). 113 | # 114 | # 'papersize': 'letterpaper', 115 | 116 | # The font size ('10pt', '11pt' or '12pt'). 117 | # 118 | # 'pointsize': '10pt', 119 | 120 | # Additional stuff for the LaTeX preamble. 121 | # 122 | # 'preamble': '', 123 | 124 | # Latex figure (float) alignment 125 | # 126 | # 'figure_align': 'htbp', 127 | } 128 | 129 | # Grouping the document tree into LaTeX files. List of tuples 130 | # (source start file, target name, title, 131 | # author, documentclass [howto, manual, or own class]). 132 | latex_documents = [ 133 | (master_doc, 'cpp-starter-project.tex', 'cpp-starter-project Documentation', 134 | 'Andre Meyering', 'manual'), 135 | ] 136 | 137 | 138 | # -- Options for manual page output ------------------------------------------ 139 | 140 | # One entry per manual page. List of tuples 141 | # (source start file, name, description, authors, manual section). 142 | man_pages = [ 143 | (master_doc, 'cpp-starter-project', 'cpp-starter-project Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ---------------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'cpp-starter-project', 'cpp-starter-project Documentation', 155 | author, 'cpp-starter-project', 'One line description of project.', 156 | 'Miscellaneous'), 157 | ] 158 | 159 | 160 | # -- Options for Epub output ------------------------------------------------- 161 | 162 | # Bibliographic Dublin Core info. 163 | epub_title = project 164 | 165 | # The unique identifier of the text. This can be a ISBN number 166 | # or the project homepage. 167 | # 168 | # epub_identifier = '' 169 | 170 | # A unique identification for the text. 171 | # 172 | # epub_uid = '' 173 | 174 | # A list of files that should not be packed into the epub file. 175 | epub_exclude_files = ['search.html'] 176 | 177 | 178 | # -- Extension configuration ------------------------------------------------- 179 | -------------------------------------------------------------------------------- /docs/user/source/contributing/bug-reports.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Bug Reports 3 | ============ 4 | 5 | You found a bug? Time to report it! 6 | 7 | This project's issue tracker is available on GitHub: 8 | https://github.com/bugwelle/cpp-starter-project/issues 9 | 10 | When opening a new issue, please send us: 11 | 12 | - the name of your operating system plus its version (e.g. Windows 8.1, Linux Mint 19, etc.) 13 | - steps to reproduce the bug 14 | 15 | We'll try to fix reported bugs as soon as possible! 16 | -------------------------------------------------------------------------------- /docs/user/source/contributing/index.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | .. warning:: 6 | 7 | Work in progress 8 | 9 | cpp-starter-project is open source! Would you like to help make 10 | cpp-starter-project better? Then this section is for you: 11 | 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | Report bugs 17 | -------------------------------------------------------------------------------- /docs/user/source/download.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Download 3 | ======== 4 | 5 | Binaries are available for macOS, Linux (AppImage_) and Windows. 6 | They can be downloaded at https://bintray.com/bugwelle/cpp-starter-project (choose your system -> click on tab "Files" -> select version). 7 | 8 | +-------------------+-------------------+-----------------------------------------------+ 9 | | System | Version | Download | 10 | +===================+===================+===============================================+ 11 | | Windows | latest (unstable) | |bintray-win| | 12 | +-------------------+-------------------+-----------------------------------------------+ 13 | | macOS | latest (unstable) | |bintray-mac| | 14 | +-------------------+-------------------+-----------------------------------------------+ 15 | | Linux (AppImage) | latest (unstable) | |bintray-linux| | 16 | +-------------------+-------------------+-----------------------------------------------+ 17 | 18 | .. _AppImage: https://appimage.org/ 19 | 20 | .. |bintray-win| image:: https://api.bintray.com/packages/bugwelle/cpp-starter-project/cpp-starter-project-win/images/download.svg 21 | :target: https://bintray.com/bugwelle/cpp-starter-project/cpp-starter-project-win/_latestVersion 22 | 23 | .. |bintray-mac| image:: https://api.bintray.com/packages/bugwelle/cpp-starter-project/cpp-starter-project-macOS/images/download.svg 24 | :target: https://bintray.com/bugwelle/cpp-starter-project/cpp-starter-project-macOS/_latestVersion 25 | 26 | .. |bintray-linux| image:: https://api.bintray.com/packages/bugwelle/cpp-starter-project/cpp-starter-project-linux/images/download.svg 27 | :target: https://bintray.com/bugwelle/cpp-starter-project/cpp-starter-project-linux/_latestVersion 28 | -------------------------------------------------------------------------------- /docs/user/source/faq.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | FAQ - Frequently Asked Questions 3 | ================================ 4 | 5 | .. contents:: 6 | :local: 7 | :depth: 1 8 | 9 | How to do X? 10 | ===================================== 11 | 12 | Typical Stackoverflow answer: Just do Y. 13 | -------------------------------------------------------------------------------- /docs/user/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to cpp-starter-project's documentation! 2 | =============================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | :caption: cpp-starter-project 7 | 8 | about 9 | download 10 | release-notes 11 | contributing/index 12 | license 13 | 14 | .. toctree:: 15 | :caption: Getting Started 16 | :maxdepth: 1 17 | :hidden: 18 | 19 | FAQ 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | -------------------------------------------------------------------------------- /docs/user/source/license.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | License 3 | ======= 4 | 5 | cpp-starter-project is licensed under the MIT License. 6 | 7 | Please refer to https://github.com/bugwelle/cpp-starter-project/blob/main/LICENSE 8 | for full license text. 9 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # Some interface targets for third-party dependencies 3 | add_library(clara INTERFACE) 4 | # "SYSTEM" so we don't get compiler warnings for it 5 | target_include_directories(clara SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 6 | -------------------------------------------------------------------------------- /external/clara/clara.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Two Blue Cubes Ltd. All rights reserved. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | // See https://github.com/philsquared/Clara for more details 7 | 8 | // Clara v1.1.5 9 | 10 | #ifndef CLARA_HPP_INCLUDED 11 | #define CLARA_HPP_INCLUDED 12 | 13 | #ifndef CLARA_CONFIG_CONSOLE_WIDTH 14 | #define CLARA_CONFIG_CONSOLE_WIDTH 80 15 | #endif 16 | 17 | #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 18 | #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH 19 | #endif 20 | 21 | #ifndef CLARA_CONFIG_OPTIONAL_TYPE 22 | #ifdef __has_include 23 | #if __has_include() && __cplusplus >= 201703L 24 | #include 25 | #define CLARA_CONFIG_OPTIONAL_TYPE std::optional 26 | #endif 27 | #endif 28 | #endif 29 | 30 | // ----------- #included from clara_textflow.hpp ----------- 31 | 32 | // TextFlowCpp 33 | // 34 | // A single-header library for wrapping and laying out basic text, by Phil Nash 35 | // 36 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 37 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 38 | // 39 | // This project is hosted at https://github.com/philsquared/textflowcpp 40 | 41 | #ifndef CLARA_TEXTFLOW_HPP_INCLUDED 42 | #define CLARA_TEXTFLOW_HPP_INCLUDED 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 50 | #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 51 | #endif 52 | 53 | namespace clara { 54 | namespace TextFlow { 55 | 56 | inline auto isWhitespace(char c) -> bool 57 | { 58 | static std::string chars = " \t\n\r"; 59 | return chars.find(c) != std::string::npos; 60 | } 61 | inline auto isBreakableBefore(char c) -> bool 62 | { 63 | static std::string chars = "[({<|"; 64 | return chars.find(c) != std::string::npos; 65 | } 66 | inline auto isBreakableAfter(char c) -> bool 67 | { 68 | static std::string chars = "])}>.,:;*+-=&/\\"; 69 | return chars.find(c) != std::string::npos; 70 | } 71 | 72 | class Columns; 73 | 74 | class Column { 75 | std::vector m_strings; 76 | size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; 77 | size_t m_indent = 0; 78 | size_t m_initialIndent = std::string::npos; 79 | 80 | public: 81 | class iterator { 82 | friend Column; 83 | 84 | Column const& m_column; 85 | size_t m_stringIndex = 0; 86 | size_t m_pos = 0; 87 | 88 | size_t m_len = 0; 89 | size_t m_end = 0; 90 | bool m_suffix = false; 91 | 92 | iterator(Column const& column, size_t stringIndex) 93 | : m_column(column) 94 | , m_stringIndex(stringIndex) 95 | {} 96 | 97 | auto line() const -> std::string const& 98 | { 99 | return m_column.m_strings[m_stringIndex]; 100 | } 101 | 102 | auto isBoundary(size_t at) const -> bool 103 | { 104 | assert(at > 0); 105 | assert(at <= line().size()); 106 | 107 | return at == line().size() || (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) 108 | || isBreakableBefore(line()[at]) || isBreakableAfter(line()[at - 1]); 109 | } 110 | 111 | void calcLength() 112 | { 113 | assert(m_stringIndex < m_column.m_strings.size()); 114 | 115 | m_suffix = false; 116 | auto width = m_column.m_width - indent(); 117 | m_end = m_pos; 118 | while (m_end < line().size() && line()[m_end] != '\n') 119 | ++m_end; 120 | 121 | if (m_end < m_pos + width) { 122 | m_len = m_end - m_pos; 123 | } else { 124 | size_t len = width; 125 | while (len > 0 && !isBoundary(m_pos + len)) 126 | --len; 127 | while (len > 0 && isWhitespace(line()[m_pos + len - 1])) 128 | --len; 129 | 130 | if (len > 0) { 131 | m_len = len; 132 | } else { 133 | m_suffix = true; 134 | m_len = width - 1; 135 | } 136 | } 137 | } 138 | 139 | auto indent() const -> size_t 140 | { 141 | auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; 142 | return initial == std::string::npos ? m_column.m_indent : initial; 143 | } 144 | 145 | auto addIndentAndSuffix(std::string const& plain) const -> std::string 146 | { 147 | return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); 148 | } 149 | 150 | public: 151 | using difference_type = std::ptrdiff_t; 152 | using value_type = std::string; 153 | using pointer = value_type*; 154 | using reference = value_type&; 155 | using iterator_category = std::forward_iterator_tag; 156 | 157 | explicit iterator(Column const& column) 158 | : m_column(column) 159 | { 160 | assert(m_column.m_width > m_column.m_indent); 161 | assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); 162 | calcLength(); 163 | if (m_len == 0) 164 | m_stringIndex++; // Empty string 165 | } 166 | 167 | auto operator*() const -> std::string 168 | { 169 | assert(m_stringIndex < m_column.m_strings.size()); 170 | assert(m_pos <= m_end); 171 | return addIndentAndSuffix(line().substr(m_pos, m_len)); 172 | } 173 | 174 | auto operator++() -> iterator& 175 | { 176 | m_pos += m_len; 177 | if (m_pos < line().size() && line()[m_pos] == '\n') 178 | m_pos += 1; 179 | else 180 | while (m_pos < line().size() && isWhitespace(line()[m_pos])) 181 | ++m_pos; 182 | 183 | if (m_pos == line().size()) { 184 | m_pos = 0; 185 | ++m_stringIndex; 186 | } 187 | if (m_stringIndex < m_column.m_strings.size()) 188 | calcLength(); 189 | return *this; 190 | } 191 | auto operator++(int) -> iterator 192 | { 193 | iterator prev(*this); 194 | operator++(); 195 | return prev; 196 | } 197 | 198 | auto operator==(iterator const& other) const -> bool 199 | { 200 | return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex && &m_column == &other.m_column; 201 | } 202 | auto operator!=(iterator const& other) const -> bool 203 | { 204 | return !operator==(other); 205 | } 206 | }; 207 | using const_iterator = iterator; 208 | 209 | explicit Column(std::string const& text) 210 | { 211 | m_strings.push_back(text); 212 | } 213 | 214 | auto width(size_t newWidth) -> Column& 215 | { 216 | assert(newWidth > 0); 217 | m_width = newWidth; 218 | return *this; 219 | } 220 | auto indent(size_t newIndent) -> Column& 221 | { 222 | m_indent = newIndent; 223 | return *this; 224 | } 225 | auto initialIndent(size_t newIndent) -> Column& 226 | { 227 | m_initialIndent = newIndent; 228 | return *this; 229 | } 230 | 231 | auto width() const -> size_t 232 | { 233 | return m_width; 234 | } 235 | auto begin() const -> iterator 236 | { 237 | return iterator(*this); 238 | } 239 | auto end() const -> iterator 240 | { 241 | return { *this, m_strings.size() }; 242 | } 243 | 244 | inline friend std::ostream& operator<<(std::ostream& os, Column const& col) 245 | { 246 | bool first = true; 247 | for (auto line : col) { 248 | if (first) 249 | first = false; 250 | else 251 | os << "\n"; 252 | os << line; 253 | } 254 | return os; 255 | } 256 | 257 | auto operator+(Column const& other) -> Columns; 258 | 259 | auto toString() const -> std::string 260 | { 261 | std::ostringstream oss; 262 | oss << *this; 263 | return oss.str(); 264 | } 265 | }; 266 | 267 | class Spacer : public Column { 268 | 269 | public: 270 | explicit Spacer(size_t spaceWidth) 271 | : Column("") 272 | { 273 | width(spaceWidth); 274 | } 275 | }; 276 | 277 | class Columns { 278 | std::vector m_columns; 279 | 280 | public: 281 | class iterator { 282 | friend Columns; 283 | struct EndTag {}; 284 | 285 | std::vector const& m_columns; 286 | std::vector m_iterators; 287 | size_t m_activeIterators; 288 | 289 | iterator(Columns const& columns, EndTag) 290 | : m_columns(columns.m_columns) 291 | , m_activeIterators(0) 292 | { 293 | m_iterators.reserve(m_columns.size()); 294 | 295 | for (auto const& col : m_columns) 296 | m_iterators.push_back(col.end()); 297 | } 298 | 299 | public: 300 | using difference_type = std::ptrdiff_t; 301 | using value_type = std::string; 302 | using pointer = value_type*; 303 | using reference = value_type&; 304 | using iterator_category = std::forward_iterator_tag; 305 | 306 | explicit iterator(Columns const& columns) 307 | : m_columns(columns.m_columns) 308 | , m_activeIterators(m_columns.size()) 309 | { 310 | m_iterators.reserve(m_columns.size()); 311 | 312 | for (auto const& col : m_columns) 313 | m_iterators.push_back(col.begin()); 314 | } 315 | 316 | auto operator==(iterator const& other) const -> bool 317 | { 318 | return m_iterators == other.m_iterators; 319 | } 320 | auto operator!=(iterator const& other) const -> bool 321 | { 322 | return m_iterators != other.m_iterators; 323 | } 324 | auto operator*() const -> std::string 325 | { 326 | std::string row, padding; 327 | 328 | for (size_t i = 0; i < m_columns.size(); ++i) { 329 | auto width = m_columns[i].width(); 330 | if (m_iterators[i] != m_columns[i].end()) { 331 | std::string col = *m_iterators[i]; 332 | row += padding + col; 333 | if (col.size() < width) 334 | padding = std::string(width - col.size(), ' '); 335 | else 336 | padding = ""; 337 | } else { 338 | padding += std::string(width, ' '); 339 | } 340 | } 341 | return row; 342 | } 343 | auto operator++() -> iterator& 344 | { 345 | for (size_t i = 0; i < m_columns.size(); ++i) { 346 | if (m_iterators[i] != m_columns[i].end()) 347 | ++m_iterators[i]; 348 | } 349 | return *this; 350 | } 351 | auto operator++(int) -> iterator 352 | { 353 | iterator prev(*this); 354 | operator++(); 355 | return prev; 356 | } 357 | }; 358 | using const_iterator = iterator; 359 | 360 | auto begin() const -> iterator 361 | { 362 | return iterator(*this); 363 | } 364 | auto end() const -> iterator 365 | { 366 | return { *this, iterator::EndTag() }; 367 | } 368 | 369 | auto operator+=(Column const& col) -> Columns& 370 | { 371 | m_columns.push_back(col); 372 | return *this; 373 | } 374 | auto operator+(Column const& col) -> Columns 375 | { 376 | Columns combined = *this; 377 | combined += col; 378 | return combined; 379 | } 380 | 381 | inline friend std::ostream& operator<<(std::ostream& os, Columns const& cols) 382 | { 383 | 384 | bool first = true; 385 | for (auto line : cols) { 386 | if (first) 387 | first = false; 388 | else 389 | os << "\n"; 390 | os << line; 391 | } 392 | return os; 393 | } 394 | 395 | auto toString() const -> std::string 396 | { 397 | std::ostringstream oss; 398 | oss << *this; 399 | return oss.str(); 400 | } 401 | }; 402 | 403 | inline auto Column::operator+(Column const& other) -> Columns 404 | { 405 | Columns cols; 406 | cols += *this; 407 | cols += other; 408 | return cols; 409 | } 410 | } // namespace TextFlow 411 | } // namespace clara 412 | 413 | #endif // CLARA_TEXTFLOW_HPP_INCLUDED 414 | 415 | // ----------- end of #include from clara_textflow.hpp ----------- 416 | // ........... back in clara.hpp 417 | 418 | #include 419 | #include 420 | #include 421 | 422 | #if !defined(CLARA_PLATFORM_WINDOWS) && (defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)) 423 | #define CLARA_PLATFORM_WINDOWS 424 | #endif 425 | 426 | namespace clara { 427 | namespace detail { 428 | 429 | // Traits for extracting arg and return type of lambdas (for single argument lambdas) 430 | template 431 | struct UnaryLambdaTraits : UnaryLambdaTraits {}; 432 | 433 | template 434 | struct UnaryLambdaTraits { 435 | static const bool isValid = false; 436 | }; 437 | 438 | template 439 | struct UnaryLambdaTraits { 440 | static const bool isValid = true; 441 | using ArgType = typename std::remove_const::type>::type; 442 | using ReturnType = ReturnT; 443 | }; 444 | 445 | class TokenStream; 446 | 447 | // Transport for raw args (copied from main args, or supplied via init list for testing) 448 | class Args { 449 | friend TokenStream; 450 | std::string m_exeName; 451 | std::vector m_args; 452 | 453 | public: 454 | Args(int argc, char const* const* argv) 455 | : m_exeName(argv[0]) 456 | , m_args(argv + 1, argv + argc) 457 | {} 458 | 459 | Args(std::initializer_list args) 460 | : m_exeName(*args.begin()) 461 | , m_args(args.begin() + 1, args.end()) 462 | {} 463 | 464 | auto exeName() const -> std::string 465 | { 466 | return m_exeName; 467 | } 468 | }; 469 | 470 | // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string 471 | // may encode an option + its argument if the : or = form is used 472 | enum class TokenType { Option, Argument }; 473 | struct Token { 474 | TokenType type; 475 | std::string token; 476 | }; 477 | 478 | inline auto isOptPrefix(char c) -> bool 479 | { 480 | return c == '-' 481 | #ifdef CLARA_PLATFORM_WINDOWS 482 | || c == '/' 483 | #endif 484 | ; 485 | } 486 | 487 | // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled 488 | class TokenStream { 489 | using Iterator = std::vector::const_iterator; 490 | Iterator it; 491 | Iterator itEnd; 492 | std::vector m_tokenBuffer; 493 | 494 | void loadBuffer() 495 | { 496 | m_tokenBuffer.resize(0); 497 | 498 | // Skip any empty strings 499 | while (it != itEnd && it->empty()) 500 | ++it; 501 | 502 | if (it != itEnd) { 503 | auto const& next = *it; 504 | if (isOptPrefix(next[0])) { 505 | auto delimiterPos = next.find_first_of(" :="); 506 | if (delimiterPos != std::string::npos) { 507 | m_tokenBuffer.push_back({ TokenType::Option, next.substr(0, delimiterPos) }); 508 | m_tokenBuffer.push_back({ TokenType::Argument, next.substr(delimiterPos + 1) }); 509 | } else { 510 | if (next[1] != '-' && next.size() > 2) { 511 | std::string opt = "- "; 512 | for (size_t i = 1; i < next.size(); ++i) { 513 | opt[1] = next[i]; 514 | m_tokenBuffer.push_back({ TokenType::Option, opt }); 515 | } 516 | } else { 517 | m_tokenBuffer.push_back({ TokenType::Option, next }); 518 | } 519 | } 520 | } else { 521 | m_tokenBuffer.push_back({ TokenType::Argument, next }); 522 | } 523 | } 524 | } 525 | 526 | public: 527 | explicit TokenStream(Args const& args) 528 | : TokenStream(args.m_args.begin(), args.m_args.end()) 529 | {} 530 | 531 | TokenStream(Iterator it, Iterator itEnd) 532 | : it(it) 533 | , itEnd(itEnd) 534 | { 535 | loadBuffer(); 536 | } 537 | 538 | explicit operator bool() const 539 | { 540 | return !m_tokenBuffer.empty() || it != itEnd; 541 | } 542 | 543 | auto count() const -> size_t 544 | { 545 | return m_tokenBuffer.size() + (itEnd - it); 546 | } 547 | 548 | auto operator*() const -> Token 549 | { 550 | assert(!m_tokenBuffer.empty()); 551 | return m_tokenBuffer.front(); 552 | } 553 | 554 | auto operator-> () const -> Token const* 555 | { 556 | assert(!m_tokenBuffer.empty()); 557 | return &m_tokenBuffer.front(); 558 | } 559 | 560 | auto operator++() -> TokenStream& 561 | { 562 | if (m_tokenBuffer.size() >= 2) { 563 | m_tokenBuffer.erase(m_tokenBuffer.begin()); 564 | } else { 565 | if (it != itEnd) 566 | ++it; 567 | loadBuffer(); 568 | } 569 | return *this; 570 | } 571 | }; 572 | 573 | class ResultBase { 574 | public: 575 | enum Type { Ok, LogicError, RuntimeError }; 576 | 577 | protected: 578 | ResultBase(Type type) 579 | : m_type(type) 580 | {} 581 | virtual ~ResultBase() = default; 582 | 583 | virtual void enforceOk() const = 0; 584 | 585 | Type m_type; 586 | }; 587 | 588 | template 589 | class ResultValueBase : public ResultBase { 590 | public: 591 | auto value() const -> T const& 592 | { 593 | enforceOk(); 594 | return m_value; 595 | } 596 | 597 | protected: 598 | ResultValueBase(Type type) 599 | : ResultBase(type) 600 | {} 601 | 602 | ResultValueBase(ResultValueBase const& other) 603 | : ResultBase(other) 604 | { 605 | if (m_type == ResultBase::Ok) 606 | new (&m_value) T(other.m_value); 607 | } 608 | 609 | ResultValueBase(Type, T const& value) 610 | : ResultBase(Ok) 611 | { 612 | new (&m_value) T(value); 613 | } 614 | 615 | auto operator=(ResultValueBase const& other) -> ResultValueBase& 616 | { 617 | if (m_type == ResultBase::Ok) 618 | m_value.~T(); 619 | ResultBase::operator=(other); 620 | if (m_type == ResultBase::Ok) 621 | new (&m_value) T(other.m_value); 622 | return *this; 623 | } 624 | 625 | ~ResultValueBase() override 626 | { 627 | if (m_type == Ok) 628 | m_value.~T(); 629 | } 630 | 631 | union { 632 | T m_value; 633 | }; 634 | }; 635 | 636 | template <> 637 | class ResultValueBase : public ResultBase { 638 | protected: 639 | using ResultBase::ResultBase; 640 | }; 641 | 642 | template 643 | class BasicResult : public ResultValueBase { 644 | public: 645 | template 646 | explicit BasicResult(BasicResult const& other) 647 | : ResultValueBase(other.type()) 648 | , m_errorMessage(other.errorMessage()) 649 | { 650 | assert(type() != ResultBase::Ok); 651 | } 652 | 653 | template 654 | static auto ok(U const& value) -> BasicResult 655 | { 656 | return { ResultBase::Ok, value }; 657 | } 658 | static auto ok() -> BasicResult 659 | { 660 | return { ResultBase::Ok }; 661 | } 662 | static auto logicError(std::string const& message) -> BasicResult 663 | { 664 | return { ResultBase::LogicError, message }; 665 | } 666 | static auto runtimeError(std::string const& message) -> BasicResult 667 | { 668 | return { ResultBase::RuntimeError, message }; 669 | } 670 | 671 | explicit operator bool() const 672 | { 673 | return m_type == ResultBase::Ok; 674 | } 675 | auto type() const -> ResultBase::Type 676 | { 677 | return m_type; 678 | } 679 | auto errorMessage() const -> std::string 680 | { 681 | return m_errorMessage; 682 | } 683 | 684 | protected: 685 | void enforceOk() const override 686 | { 687 | 688 | // Errors shouldn't reach this point, but if they do 689 | // the actual error message will be in m_errorMessage 690 | assert(m_type != ResultBase::LogicError); 691 | assert(m_type != ResultBase::RuntimeError); 692 | if (m_type != ResultBase::Ok) 693 | std::abort(); 694 | } 695 | 696 | std::string m_errorMessage; // Only populated if resultType is an error 697 | 698 | BasicResult(ResultBase::Type type, std::string const& message) 699 | : ResultValueBase(type) 700 | , m_errorMessage(message) 701 | { 702 | assert(m_type != ResultBase::Ok); 703 | } 704 | 705 | using ResultValueBase::ResultValueBase; 706 | using ResultBase::m_type; 707 | }; 708 | 709 | enum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame }; 710 | 711 | class ParseState { 712 | public: 713 | ParseState(ParseResultType type, TokenStream const& remainingTokens) 714 | : m_type(type) 715 | , m_remainingTokens(remainingTokens) 716 | {} 717 | 718 | auto type() const -> ParseResultType 719 | { 720 | return m_type; 721 | } 722 | auto remainingTokens() const -> TokenStream 723 | { 724 | return m_remainingTokens; 725 | } 726 | 727 | private: 728 | ParseResultType m_type; 729 | TokenStream m_remainingTokens; 730 | }; 731 | 732 | using Result = BasicResult; 733 | using ParserResult = BasicResult; 734 | using InternalParseResult = BasicResult; 735 | 736 | struct HelpColumns { 737 | std::string left; 738 | std::string right; 739 | }; 740 | 741 | template 742 | inline auto convertInto(std::string const& source, T& target) -> ParserResult 743 | { 744 | std::stringstream ss; 745 | ss << source; 746 | ss >> target; 747 | if (ss.fail()) 748 | return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type"); 749 | else 750 | return ParserResult::ok(ParseResultType::Matched); 751 | } 752 | inline auto convertInto(std::string const& source, std::string& target) -> ParserResult 753 | { 754 | target = source; 755 | return ParserResult::ok(ParseResultType::Matched); 756 | } 757 | inline auto convertInto(std::string const& source, bool& target) -> ParserResult 758 | { 759 | std::string srcLC = source; 760 | std::transform( 761 | srcLC.begin(), srcLC.end(), srcLC.begin(), [](char c) { return static_cast(::tolower(c)); }); 762 | if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") 763 | target = true; 764 | else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") 765 | target = false; 766 | else 767 | return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" + source + "'"); 768 | return ParserResult::ok(ParseResultType::Matched); 769 | } 770 | #ifdef CLARA_CONFIG_OPTIONAL_TYPE 771 | template 772 | inline auto convertInto(std::string const& source, CLARA_CONFIG_OPTIONAL_TYPE& target) -> ParserResult 773 | { 774 | T temp; 775 | auto result = convertInto(source, temp); 776 | if (result) 777 | target = std::move(temp); 778 | return result; 779 | } 780 | #endif // CLARA_CONFIG_OPTIONAL_TYPE 781 | 782 | struct NonCopyable { 783 | NonCopyable() = default; 784 | NonCopyable(NonCopyable const&) = delete; 785 | NonCopyable(NonCopyable&&) = delete; 786 | NonCopyable& operator=(NonCopyable const&) = delete; 787 | NonCopyable& operator=(NonCopyable&&) = delete; 788 | }; 789 | 790 | struct BoundRef : NonCopyable { 791 | virtual ~BoundRef() = default; 792 | virtual auto isContainer() const -> bool 793 | { 794 | return false; 795 | } 796 | virtual auto isFlag() const -> bool 797 | { 798 | return false; 799 | } 800 | }; 801 | struct BoundValueRefBase : BoundRef { 802 | virtual auto setValue(std::string const& arg) -> ParserResult = 0; 803 | }; 804 | struct BoundFlagRefBase : BoundRef { 805 | virtual auto setFlag(bool flag) -> ParserResult = 0; 806 | virtual auto isFlag() const -> bool 807 | { 808 | return true; 809 | } 810 | }; 811 | 812 | template 813 | struct BoundValueRef : BoundValueRefBase { 814 | T& m_ref; 815 | 816 | explicit BoundValueRef(T& ref) 817 | : m_ref(ref) 818 | {} 819 | 820 | auto setValue(std::string const& arg) -> ParserResult override 821 | { 822 | return convertInto(arg, m_ref); 823 | } 824 | }; 825 | 826 | template 827 | struct BoundValueRef> : BoundValueRefBase { 828 | std::vector& m_ref; 829 | 830 | explicit BoundValueRef(std::vector& ref) 831 | : m_ref(ref) 832 | {} 833 | 834 | auto isContainer() const -> bool override 835 | { 836 | return true; 837 | } 838 | 839 | auto setValue(std::string const& arg) -> ParserResult override 840 | { 841 | T temp; 842 | auto result = convertInto(arg, temp); 843 | if (result) 844 | m_ref.push_back(temp); 845 | return result; 846 | } 847 | }; 848 | 849 | struct BoundFlagRef : BoundFlagRefBase { 850 | bool& m_ref; 851 | 852 | explicit BoundFlagRef(bool& ref) 853 | : m_ref(ref) 854 | {} 855 | 856 | auto setFlag(bool flag) -> ParserResult override 857 | { 858 | m_ref = flag; 859 | return ParserResult::ok(ParseResultType::Matched); 860 | } 861 | }; 862 | 863 | template 864 | struct LambdaInvoker { 865 | static_assert(std::is_same::value, "Lambda must return void or clara::ParserResult"); 866 | 867 | template 868 | static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult 869 | { 870 | return lambda(arg); 871 | } 872 | }; 873 | 874 | template <> 875 | struct LambdaInvoker { 876 | template 877 | static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult 878 | { 879 | lambda(arg); 880 | return ParserResult::ok(ParseResultType::Matched); 881 | } 882 | }; 883 | 884 | template 885 | inline auto invokeLambda(L const& lambda, std::string const& arg) -> ParserResult 886 | { 887 | ArgType temp {}; 888 | auto result = convertInto(arg, temp); 889 | return !result ? result : LambdaInvoker::ReturnType>::invoke(lambda, temp); 890 | } 891 | 892 | template 893 | struct BoundLambda : BoundValueRefBase { 894 | L m_lambda; 895 | 896 | static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); 897 | explicit BoundLambda(L const& lambda) 898 | : m_lambda(lambda) 899 | {} 900 | 901 | auto setValue(std::string const& arg) -> ParserResult override 902 | { 903 | return invokeLambda::ArgType>(m_lambda, arg); 904 | } 905 | }; 906 | 907 | template 908 | struct BoundFlagLambda : BoundFlagRefBase { 909 | L m_lambda; 910 | 911 | static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); 912 | static_assert(std::is_same::ArgType, bool>::value, "flags must be boolean"); 913 | 914 | explicit BoundFlagLambda(L const& lambda) 915 | : m_lambda(lambda) 916 | {} 917 | 918 | auto setFlag(bool flag) -> ParserResult override 919 | { 920 | return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); 921 | } 922 | }; 923 | 924 | enum class Optionality { Optional, Required }; 925 | 926 | struct Parser; 927 | 928 | class ParserBase { 929 | public: 930 | virtual ~ParserBase() = default; 931 | virtual auto validate() const -> Result 932 | { 933 | return Result::ok(); 934 | } 935 | virtual auto parse(std::string const& exeName, TokenStream const& tokens) const -> InternalParseResult = 0; 936 | virtual auto cardinality() const -> size_t 937 | { 938 | return 1; 939 | } 940 | 941 | auto parse(Args const& args) const -> InternalParseResult 942 | { 943 | return parse(args.exeName(), TokenStream(args)); 944 | } 945 | }; 946 | 947 | template 948 | class ComposableParserImpl : public ParserBase { 949 | public: 950 | template 951 | auto operator|(T const& other) const -> Parser; 952 | 953 | template 954 | auto operator+(T const& other) const -> Parser; 955 | }; 956 | 957 | // Common code and state for Args and Opts 958 | template 959 | class ParserRefImpl : public ComposableParserImpl { 960 | protected: 961 | Optionality m_optionality = Optionality::Optional; 962 | std::shared_ptr m_ref; 963 | std::string m_hint; 964 | std::string m_description; 965 | 966 | explicit ParserRefImpl(std::shared_ptr const& ref) 967 | : m_ref(ref) 968 | {} 969 | 970 | public: 971 | template 972 | ParserRefImpl(T& ref, std::string const& hint) 973 | : m_ref(std::make_shared>(ref)) 974 | , m_hint(hint) 975 | {} 976 | 977 | template 978 | ParserRefImpl(LambdaT const& ref, std::string const& hint) 979 | : m_ref(std::make_shared>(ref)) 980 | , m_hint(hint) 981 | {} 982 | 983 | auto operator()(std::string const& description) -> DerivedT& 984 | { 985 | m_description = description; 986 | return static_cast(*this); 987 | } 988 | 989 | auto optional() -> DerivedT& 990 | { 991 | m_optionality = Optionality::Optional; 992 | return static_cast(*this); 993 | }; 994 | 995 | auto required() -> DerivedT& 996 | { 997 | m_optionality = Optionality::Required; 998 | return static_cast(*this); 999 | }; 1000 | 1001 | auto isOptional() const -> bool 1002 | { 1003 | return m_optionality == Optionality::Optional; 1004 | } 1005 | 1006 | auto cardinality() const -> size_t override 1007 | { 1008 | if (m_ref->isContainer()) 1009 | return 0; 1010 | else 1011 | return 1; 1012 | } 1013 | 1014 | auto hint() const -> std::string 1015 | { 1016 | return m_hint; 1017 | } 1018 | }; 1019 | 1020 | class ExeName : public ComposableParserImpl { 1021 | std::shared_ptr m_name; 1022 | std::shared_ptr m_ref; 1023 | 1024 | template 1025 | static auto makeRef(LambdaT const& lambda) -> std::shared_ptr 1026 | { 1027 | return std::make_shared>(lambda); 1028 | } 1029 | 1030 | public: 1031 | ExeName() 1032 | : m_name(std::make_shared("")) 1033 | {} 1034 | 1035 | explicit ExeName(std::string& ref) 1036 | : ExeName() 1037 | { 1038 | m_ref = std::make_shared>(ref); 1039 | } 1040 | 1041 | template 1042 | explicit ExeName(LambdaT const& lambda) 1043 | : ExeName() 1044 | { 1045 | m_ref = std::make_shared>(lambda); 1046 | } 1047 | 1048 | // The exe name is not parsed out of the normal tokens, but is handled specially 1049 | auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override 1050 | { 1051 | return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); 1052 | } 1053 | 1054 | auto name() const -> std::string 1055 | { 1056 | return *m_name; 1057 | } 1058 | auto set(std::string const& newName) -> ParserResult 1059 | { 1060 | 1061 | auto lastSlash = newName.find_last_of("\\/"); 1062 | auto filename = (lastSlash == std::string::npos) ? newName : newName.substr(lastSlash + 1); 1063 | 1064 | *m_name = filename; 1065 | if (m_ref) 1066 | return m_ref->setValue(filename); 1067 | else 1068 | return ParserResult::ok(ParseResultType::Matched); 1069 | } 1070 | }; 1071 | 1072 | class Arg : public ParserRefImpl { 1073 | public: 1074 | using ParserRefImpl::ParserRefImpl; 1075 | 1076 | auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override 1077 | { 1078 | auto validationResult = validate(); 1079 | if (!validationResult) 1080 | return InternalParseResult(validationResult); 1081 | 1082 | auto remainingTokens = tokens; 1083 | auto const& token = *remainingTokens; 1084 | if (token.type != TokenType::Argument) 1085 | return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); 1086 | 1087 | assert(!m_ref->isFlag()); 1088 | auto valueRef = static_cast(m_ref.get()); 1089 | 1090 | auto result = valueRef->setValue(remainingTokens->token); 1091 | if (!result) 1092 | return InternalParseResult(result); 1093 | else 1094 | return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); 1095 | } 1096 | }; 1097 | 1098 | inline auto normaliseOpt(std::string const& optName) -> std::string 1099 | { 1100 | #ifdef CLARA_PLATFORM_WINDOWS 1101 | if (optName[0] == '/') 1102 | return "-" + optName.substr(1); 1103 | else 1104 | #endif 1105 | return optName; 1106 | } 1107 | 1108 | class Opt : public ParserRefImpl { 1109 | protected: 1110 | std::vector m_optNames; 1111 | 1112 | public: 1113 | template 1114 | explicit Opt(LambdaT const& ref) 1115 | : ParserRefImpl(std::make_shared>(ref)) 1116 | {} 1117 | 1118 | explicit Opt(bool& ref) 1119 | : ParserRefImpl(std::make_shared(ref)) 1120 | {} 1121 | 1122 | template 1123 | Opt(LambdaT const& ref, std::string const& hint) 1124 | : ParserRefImpl(ref, hint) 1125 | {} 1126 | 1127 | template 1128 | Opt(T& ref, std::string const& hint) 1129 | : ParserRefImpl(ref, hint) 1130 | {} 1131 | 1132 | auto operator[](std::string const& optName) -> Opt& 1133 | { 1134 | m_optNames.push_back(optName); 1135 | return *this; 1136 | } 1137 | 1138 | auto getHelpColumns() const -> std::vector 1139 | { 1140 | std::ostringstream oss; 1141 | bool first = true; 1142 | for (auto const& opt : m_optNames) { 1143 | if (first) 1144 | first = false; 1145 | else 1146 | oss << ", "; 1147 | oss << opt; 1148 | } 1149 | if (!m_hint.empty()) 1150 | oss << " <" << m_hint << ">"; 1151 | return { { oss.str(), m_description } }; 1152 | } 1153 | 1154 | auto isMatch(std::string const& optToken) const -> bool 1155 | { 1156 | auto normalisedToken = normaliseOpt(optToken); 1157 | for (auto const& name : m_optNames) { 1158 | if (normaliseOpt(name) == normalisedToken) 1159 | return true; 1160 | } 1161 | return false; 1162 | } 1163 | 1164 | using ParserBase::parse; 1165 | 1166 | auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override 1167 | { 1168 | auto validationResult = validate(); 1169 | if (!validationResult) 1170 | return InternalParseResult(validationResult); 1171 | 1172 | auto remainingTokens = tokens; 1173 | if (remainingTokens && remainingTokens->type == TokenType::Option) { 1174 | auto const& token = *remainingTokens; 1175 | if (isMatch(token.token)) { 1176 | if (m_ref->isFlag()) { 1177 | auto flagRef = static_cast(m_ref.get()); 1178 | auto result = flagRef->setFlag(true); 1179 | if (!result) 1180 | return InternalParseResult(result); 1181 | if (result.value() == ParseResultType::ShortCircuitAll) 1182 | return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); 1183 | } else { 1184 | auto valueRef = static_cast(m_ref.get()); 1185 | ++remainingTokens; 1186 | if (!remainingTokens) 1187 | return InternalParseResult::runtimeError("Expected argument following " + token.token); 1188 | auto const& argToken = *remainingTokens; 1189 | if (argToken.type != TokenType::Argument) 1190 | return InternalParseResult::runtimeError("Expected argument following " + token.token); 1191 | auto result = valueRef->setValue(argToken.token); 1192 | if (!result) 1193 | return InternalParseResult(result); 1194 | if (result.value() == ParseResultType::ShortCircuitAll) 1195 | return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); 1196 | } 1197 | return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); 1198 | } 1199 | } 1200 | return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); 1201 | } 1202 | 1203 | auto validate() const -> Result override 1204 | { 1205 | if (m_optNames.empty()) 1206 | return Result::logicError("No options supplied to Opt"); 1207 | for (auto const& name : m_optNames) { 1208 | if (name.empty()) 1209 | return Result::logicError("Option name cannot be empty"); 1210 | #ifdef CLARA_PLATFORM_WINDOWS 1211 | if (name[0] != '-' && name[0] != '/') 1212 | return Result::logicError("Option name must begin with '-' or '/'"); 1213 | #else 1214 | if (name[0] != '-') 1215 | return Result::logicError("Option name must begin with '-'"); 1216 | #endif 1217 | } 1218 | return ParserRefImpl::validate(); 1219 | } 1220 | }; 1221 | 1222 | struct Help : Opt { 1223 | Help(bool& showHelpFlag) 1224 | : Opt([&](bool flag) { 1225 | showHelpFlag = flag; 1226 | return ParserResult::ok(ParseResultType::ShortCircuitAll); 1227 | }) 1228 | { 1229 | static_cast (*this)("display usage information")["-?"]["-h"]["--help"].optional(); 1230 | } 1231 | }; 1232 | 1233 | struct Parser : ParserBase { 1234 | 1235 | mutable ExeName m_exeName; 1236 | std::vector m_options; 1237 | std::vector m_args; 1238 | 1239 | auto operator|=(ExeName const& exeName) -> Parser& 1240 | { 1241 | m_exeName = exeName; 1242 | return *this; 1243 | } 1244 | 1245 | auto operator|=(Arg const& arg) -> Parser& 1246 | { 1247 | m_args.push_back(arg); 1248 | return *this; 1249 | } 1250 | 1251 | auto operator|=(Opt const& opt) -> Parser& 1252 | { 1253 | m_options.push_back(opt); 1254 | return *this; 1255 | } 1256 | 1257 | auto operator|=(Parser const& other) -> Parser& 1258 | { 1259 | m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); 1260 | m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); 1261 | return *this; 1262 | } 1263 | 1264 | template 1265 | auto operator|(T const& other) const -> Parser 1266 | { 1267 | return Parser(*this) |= other; 1268 | } 1269 | 1270 | // Forward deprecated interface with '+' instead of '|' 1271 | template 1272 | auto operator+=(T const& other) -> Parser& 1273 | { 1274 | return operator|=(other); 1275 | } 1276 | template 1277 | auto operator+(T const& other) const -> Parser 1278 | { 1279 | return operator|(other); 1280 | } 1281 | 1282 | auto getHelpColumns() const -> std::vector 1283 | { 1284 | std::vector cols; 1285 | for (auto const& o : m_options) { 1286 | auto childCols = o.getHelpColumns(); 1287 | cols.insert(cols.end(), childCols.begin(), childCols.end()); 1288 | } 1289 | return cols; 1290 | } 1291 | 1292 | void writeToStream(std::ostream& os) const 1293 | { 1294 | if (!m_exeName.name().empty()) { 1295 | os << "usage:\n" 1296 | << " " << m_exeName.name() << " "; 1297 | bool required = true, first = true; 1298 | for (auto const& arg : m_args) { 1299 | if (first) 1300 | first = false; 1301 | else 1302 | os << " "; 1303 | if (arg.isOptional() && required) { 1304 | os << "["; 1305 | required = false; 1306 | } 1307 | os << "<" << arg.hint() << ">"; 1308 | if (arg.cardinality() == 0) 1309 | os << " ... "; 1310 | } 1311 | if (!required) 1312 | os << "]"; 1313 | if (!m_options.empty()) 1314 | os << " options"; 1315 | os << "\n\nwhere options are:" << std::endl; 1316 | } 1317 | 1318 | auto rows = getHelpColumns(); 1319 | size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; 1320 | size_t optWidth = 0; 1321 | for (auto const& cols : rows) 1322 | optWidth = (std::max)(optWidth, cols.left.size() + 2); 1323 | 1324 | optWidth = (std::min)(optWidth, consoleWidth / 2); 1325 | 1326 | for (auto const& cols : rows) { 1327 | auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) 1328 | + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); 1329 | os << row << std::endl; 1330 | } 1331 | } 1332 | 1333 | friend auto operator<<(std::ostream& os, Parser const& parser) -> std::ostream& 1334 | { 1335 | parser.writeToStream(os); 1336 | return os; 1337 | } 1338 | 1339 | auto validate() const -> Result override 1340 | { 1341 | for (auto const& opt : m_options) { 1342 | auto result = opt.validate(); 1343 | if (!result) 1344 | return result; 1345 | } 1346 | for (auto const& arg : m_args) { 1347 | auto result = arg.validate(); 1348 | if (!result) 1349 | return result; 1350 | } 1351 | return Result::ok(); 1352 | } 1353 | 1354 | using ParserBase::parse; 1355 | 1356 | auto parse(std::string const& exeName, TokenStream const& tokens) const -> InternalParseResult override 1357 | { 1358 | 1359 | struct ParserInfo { 1360 | ParserBase const* parser = nullptr; 1361 | size_t count = 0; 1362 | }; 1363 | const size_t totalParsers = m_options.size() + m_args.size(); 1364 | assert(totalParsers < 512); 1365 | // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do 1366 | ParserInfo parseInfos[512]; 1367 | 1368 | { 1369 | size_t i = 0; 1370 | for (auto const& opt : m_options) 1371 | parseInfos[i++].parser = &opt; 1372 | for (auto const& arg : m_args) 1373 | parseInfos[i++].parser = &arg; 1374 | } 1375 | 1376 | m_exeName.set(exeName); 1377 | 1378 | auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); 1379 | while (result.value().remainingTokens()) { 1380 | bool tokenParsed = false; 1381 | 1382 | for (size_t i = 0; i < totalParsers; ++i) { 1383 | auto& parseInfo = parseInfos[i]; 1384 | if (parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality()) { 1385 | result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); 1386 | if (!result) 1387 | return result; 1388 | if (result.value().type() != ParseResultType::NoMatch) { 1389 | tokenParsed = true; 1390 | ++parseInfo.count; 1391 | break; 1392 | } 1393 | } 1394 | } 1395 | 1396 | if (result.value().type() == ParseResultType::ShortCircuitAll) 1397 | return result; 1398 | if (!tokenParsed) 1399 | return InternalParseResult::runtimeError( 1400 | "Unrecognised token: " + result.value().remainingTokens()->token); 1401 | } 1402 | // !TBD Check missing required options 1403 | return result; 1404 | } 1405 | }; 1406 | 1407 | template 1408 | template 1409 | auto ComposableParserImpl::operator|(T const& other) const -> Parser 1410 | { 1411 | return Parser() | static_cast(*this) | other; 1412 | } 1413 | } // namespace detail 1414 | 1415 | // A Combined parser 1416 | using detail::Parser; 1417 | 1418 | // A parser for options 1419 | using detail::Opt; 1420 | 1421 | // A parser for arguments 1422 | using detail::Arg; 1423 | 1424 | // Wrapper for argc, argv from main() 1425 | using detail::Args; 1426 | 1427 | // Specifies the name of the executable 1428 | using detail::ExeName; 1429 | 1430 | // Convenience wrapper for option parser that specifies the help option 1431 | using detail::Help; 1432 | 1433 | // enum of result types from a parse 1434 | using detail::ParseResultType; 1435 | 1436 | // Result type for parser operation 1437 | using detail::ParserResult; 1438 | 1439 | } // namespace clara 1440 | 1441 | #endif // CLARA_HPP_INCLUDED 1442 | -------------------------------------------------------------------------------- /packaging/README.md: -------------------------------------------------------------------------------- 1 | # Packaging 2 | 3 | Packaging is done using CMake. 4 | 5 | ## Example Usage 6 | 7 | ```sh 8 | cmake -S . -B build/release -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/installed 9 | cmake --build build/release 10 | cd build/release 11 | ``` 12 | -------------------------------------------------------------------------------- /packaging/description.txt: -------------------------------------------------------------------------------- 1 | A description of the project, used in places such as the 2 | introduction screen of CPack-generated Windows installers. -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # Dependencies 3 | 4 | find_package(range-v3 0.11 QUIET) 5 | if (NOT range-v3_FOUND) 6 | # I use FetchContent here for now. You could also use: find_package(range-v3 7 | # REQUIRED) 8 | fetchcontent_declare( 9 | range-v3 10 | GIT_REPOSITORY https://github.com/ericniebler/range-v3.git 11 | GIT_TAG 0.11.0 12 | ) 13 | 14 | fetchcontent_makeavailable(range-v3) 15 | endif() 16 | 17 | # ----------------------------------------------------------------------------- 18 | # Modules 19 | add_subdirectory(math) 20 | 21 | # ----------------------------------------------------------------------------- 22 | # Main cpp_starter executable 23 | add_executable(cpp_starter) 24 | target_link_libraries(cpp_starter PRIVATE cpp_starter_math) 25 | target_link_libraries(cpp_starter PRIVATE range-v3::range-v3 clara) 26 | 27 | configure_file(version.hpp.in ${PROJECT_BINARY_DIR}/version.hpp @ONLY) 28 | 29 | # ----------------------------------------------------------------------------- 30 | # Installation 31 | install(TARGETS cpp_starter RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") 32 | 33 | # ----------------------------------------------------------------------------- 34 | # Sources 35 | target_sources(cpp_starter PRIVATE main.cpp) 36 | 37 | # ----------------------------------------------------------------------------- 38 | # Defaults 39 | set_post_target_defaults(cpp_starter) 40 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "math/fibonacci.hpp" 2 | #include "version.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | struct CliOptions { 14 | std::optional input_path; 15 | std::string command {}; 16 | bool print_help = false; 17 | }; 18 | 19 | static void print_help(const clara::Parser& parser) 20 | { 21 | std::cout << "Project: " << Project::Config::Name << '\n'; 22 | std::cout << "Version: " << Project::Config::VersionStr << '\n'; 23 | std::cout << "Version Major: " << Project::Config::VersionMajor << '\n'; 24 | std::cout << "Version Minor: " << Project::Config::VersionMinor << '\n'; 25 | std::cout << "Version Tweak: " << Project::Config::VersionPatch << '\n'; 26 | std::cout << "Git Revision: " << Project::Config::GitRevision << '\n'; 27 | std::cout << "Website: " << Project::Config::Website << '\n'; 28 | std::cout << "Description: " << Project::Config::Description << "\n\n"; 29 | std::cout << parser << std::endl; 30 | } 31 | 32 | int main(int argc, char** argv) 33 | { 34 | using namespace CppStarter; 35 | using namespace clara; 36 | CliOptions options; 37 | 38 | // clang-format off 39 | auto cli = Help(options.print_help) 40 | | Opt(options.input_path, "input path") 41 | ["-i"]["--input"] 42 | ("Prints [path] as Tokenstream") 43 | | Arg(options.command, "fibo|csv") 44 | ("which command to run"); 45 | // clang-format on 46 | 47 | auto result = cli.parse(Args(argc, argv)); 48 | if (!result) { 49 | std::cerr << "Error in command line: " << result.errorMessage() << std::endl; 50 | return 1; 51 | } 52 | 53 | if (options.print_help) { 54 | print_help(cli); 55 | return 0; 56 | } 57 | 58 | if (options.command.empty()) { 59 | std::cerr << "Missing command. Pass \"--help\" for help." << std::endl; 60 | return 1; 61 | } 62 | 63 | if (options.command == "fibo") { 64 | std::cout << Math::fibonacci(10ll) << std::endl; 65 | return 0; 66 | } 67 | 68 | if (options.command == "csv") { 69 | return 0; 70 | } 71 | 72 | std::cout << options.command; 73 | 74 | ranges::ostream_iterator out_it(std::cout, ", "); 75 | ranges::fill_n(out_it, 100, 1); 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /src/math/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(cpp_starter_math STATIC) 2 | 3 | target_sources(cpp_starter_math PRIVATE "fibonacci.cpp") 4 | 5 | set_post_target_defaults(cpp_starter_math) 6 | -------------------------------------------------------------------------------- /src/math/fibonacci.cpp: -------------------------------------------------------------------------------- 1 | #include "math/fibonacci.hpp" 2 | 3 | // Add a source (.cpp) file for each header file 4 | // even if it is otherwise empty to ensure that 5 | // each header file can be compiled separately. 6 | -------------------------------------------------------------------------------- /src/math/fibonacci.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace CppStarter::Math { 6 | 7 | /** 8 | * @brief Calculate fibonacci numbers. 9 | * 10 | * @param num nth's fibonacci number 11 | */ 12 | template >> 13 | [[nodiscard]] T fibonacci(T num) 14 | { 15 | if (num == 0) { 16 | return 0; 17 | } 18 | if (num == 1) { 19 | return 1; 20 | } 21 | 22 | return fibonacci(num - 1) + fibonacci(num - 2); 23 | } 24 | 25 | } // namespace CppStarter::Math 26 | -------------------------------------------------------------------------------- /src/version.hpp.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // "version.hpp" is created by CMake 4 | 5 | namespace Project::Config { 6 | 7 | constexpr char Name[] = "@PROJECT_NAME@"; 8 | constexpr char VersionStr[] = "@PROJECT_VERSION@"; 9 | constexpr int VersionMajor = @PROJECT_VERSION_MAJOR@; 10 | constexpr int VersionMinor = @PROJECT_VERSION_MINOR@; 11 | constexpr int VersionPatch = @PROJECT_VERSION_PATCH@; 12 | // This project does not specify a tweak version 13 | // constexpr int VersionTweak = @PROJECT_VERSION_TWEAK@; 14 | constexpr char Description[] = "@PROJECT_DESCRIPTION@"; 15 | constexpr char Website[] = "@PROJECT_HOMEPAGE_URL@"; 16 | 17 | constexpr char GitRevision[] = "@GIT_REV@"; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # Tests 3 | 4 | # I use FetchContent here because Catch2 is a small dependency, that is only 5 | # used for testing. You could also use: find_package(Catch2 REQUIRED) 6 | fetchcontent_declare( 7 | Catch2 8 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 9 | GIT_TAG v2.13.1 10 | ) 11 | fetchcontent_makeavailable(Catch2) 12 | 13 | add_executable(cpp_test) 14 | target_include_directories(cpp_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 15 | target_link_libraries(cpp_test PRIVATE Catch2::Catch2 cpp_starter_math) 16 | 17 | target_sources( 18 | cpp_test 19 | PRIVATE main.cpp 20 | math/test_fibonacci.cpp # List all test sources here. Prefix your test 21 | # sources with `test_` to avoid name clashes 22 | # with files in `src`. 23 | ) 24 | 25 | # If ENABLE_COVERAGE is set, generate a coverage report for this test. 26 | generate_coverage_report(cpp_test) 27 | 28 | # Let Catch2 find all tests so that CTest can list all tests instead of just one 29 | # executable. 30 | catch_discover_tests(cpp_test) 31 | 32 | set_post_target_defaults(cpp_test) 33 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | #include "catch2/catch.hpp" 3 | 4 | #include 5 | 6 | TEST_CASE("Test framework works", "[unit]") 7 | { 8 | CHECK('a' < 'b'); 9 | } 10 | 11 | int main(int argc, char** argv) 12 | { 13 | Catch::Session session; 14 | const int res = session.run(argc, argv); 15 | return res; 16 | } 17 | -------------------------------------------------------------------------------- /tests/math/test_fibonacci.cpp: -------------------------------------------------------------------------------- 1 | #include "test_helpers.hpp" 2 | 3 | #include "math/fibonacci.hpp" 4 | 5 | TEST_CASE("fibonacci numbers are computed", "[fibonacci]") 6 | { 7 | using namespace CppStarter::Math; 8 | CHECK(fibonacci(0) == 0); 9 | CHECK(fibonacci(1) == 1); 10 | CHECK(fibonacci(2) == 1); 11 | CHECK(fibonacci(3) == 2); 12 | CHECK(fibonacci(10) == 55); 13 | CHECK(fibonacci(20) == 6765); 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_helpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER 4 | #include "catch2/catch.hpp" 5 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(CppCheck) 2 | find_package(Shellcheck) 3 | find_package(CMakeFormat) 4 | 5 | # ----------------------------------------------------------------------------- 6 | # cppcheck 7 | if(CppCheck_FOUND) 8 | # We take advantage of our project structure and that all include paths are 9 | # relative to `src`. I dislike using CXX_CPPCHECK as it runs next to the 10 | # compiler but doesn't require compiling the source code if run as a separate 11 | # process. Distinguishing between compiler and cppcheck warnings may be 12 | # bothering but not neccessarily a bad practice. If we were to introduce a 13 | # more complex project structure with different include paths, it may be 14 | # preferable to use CXX_CPPCHECK. 15 | add_custom_target( 16 | cppcheck 17 | COMMAND ${CPPCHECK_EXE} -j2 --enable=all -I${CMAKE_SOURCE_DIR}/src -j1 18 | ${CMAKE_SOURCE_DIR}/src 19 | COMMENT "format cmake files" 20 | ) 21 | endif() 22 | 23 | # ----------------------------------------------------------------------------- 24 | # Bash script linting using shellcheck 25 | if(Shellcheck_FOUND) 26 | add_custom_target( 27 | shellcheck 28 | COMMAND ${SHELLCHECK_EXE} "${CMAKE_SOURCE_DIR}/tools/*.sh" 29 | COMMENT "Lint bash scripts using shellcheck" 30 | ) 31 | endif() 32 | 33 | # ----------------------------------------------------------------------------- 34 | # cmake-format 35 | if(CMakeFormat_FOUND) 36 | add_custom_target( 37 | cmake-format 38 | COMMAND 39 | ${CMAKE_FORMAT_EXE} -c ${CMAKE_SOURCE_DIR}/.cmake-format -i 40 | ${CMAKE_SOURCE_DIR}/CMakeLists.txt ${CMAKE_SOURCE_DIR}/docs/CMakeLists.txt 41 | ${CMAKE_SOURCE_DIR}/src/CMakeLists.txt 42 | ${CMAKE_SOURCE_DIR}/tests/CMakeLists.txt ${CMAKE_SOURCE_DIR}/cmake/*.cmake 43 | ${CMAKE_SOURCE_DIR}/tools/CMakeLists.txt 44 | COMMENT "format cmake files" 45 | ) 46 | endif() 47 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | The `tools/` directory is designated for holding extra scripts and tools 3 | related to developing and contributing to the project. For example, turn-key 4 | build scripts, linting scripts, code-generation scripts, test scripts, or 5 | other tools that may be useful to a project developer. 6 | 7 | ## Bash Scripts 8 | I like bash scripts. That's why you'll find quite a few in this directory. 9 | We could of course use CMake's custom targets to run e.g. cppcheck but 10 | I didn't have time for that, yet. :-) 11 | 12 | We use the "unofficial bash strict mode" in our bash scripts to avoid 13 | having to debug them more than necessary. 14 | See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ 15 | -------------------------------------------------------------------------------- /tools/build_run_clang_tidy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" 7 | 8 | cd "${SCRIPT_PATH}/.." 9 | source tools/utils.sh 10 | 11 | print_info "Building with clang-tidy and applying fixes" 12 | 13 | mkdir -p build 14 | rm -rf build/* 15 | cd build 16 | cmake -S .. -B . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 17 | python3 /usr/lib/llvm-*/share/clang/run-clang-tidy.py -fix ../src 18 | -------------------------------------------------------------------------------- /tools/quick_checks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Convenience script that runs all short-running checks. 4 | # Should be run before committing and pushing code. 5 | 6 | set -euo pipefail 7 | IFS=$'\n\t' 8 | 9 | cd "$(dirname "$0")" 10 | source utils.sh 11 | 12 | print_important "Run all 4 quick checks" 13 | 14 | print_info "\nRunning check 1 / 4" 15 | ./run_clang_format.sh 16 | 17 | print_info "\nRunning check 2 / 4" 18 | ./run_cmake_format.sh 19 | 20 | print_info "\nRunning check 3 / 4" 21 | ./run_shellcheck.sh 22 | 23 | print_info "\nRunning check 4 / 4" 24 | ./run_cppcheck.sh 25 | 26 | print_success "\nAll checks finished successfully!" 27 | -------------------------------------------------------------------------------- /tools/run_clang_format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -Eeuo pipefail 4 | IFS=$'\n\t' 5 | 6 | # Go to project directory 7 | cd "$(dirname "${BASH_SOURCE[0]}")/.." > /dev/null 2>&1 8 | 9 | source tools/utils.sh 10 | 11 | # Explicitly use clang-format in version 12. 12 | # As I use macOS with MacPorts, we search for it as well. 13 | if [[ -x "$(command -v clang-format-12)" ]]; then 14 | CF=clang-format-12 15 | elif [[ -x "$(command -v clang-format-mp-12)" ]]; then 16 | # MacPorts version 17 | CF=clang-format-mp-12 18 | else 19 | CF=clang-format 20 | clang-format --version | grep " 12." > /dev/null || (print_warning "WARNING: MediaElch requires clang-format version 12") 21 | fi 22 | 23 | print_important "Format all source files using ${CF}" 24 | find src tests \ 25 | -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) \ 26 | -exec ${CF} \ 27 | -i -style=file {} \+ 28 | 29 | print_success "Done" 30 | -------------------------------------------------------------------------------- /tools/run_cmake_format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | ############################################################################### 7 | # Run cmake-format on all CMake files (for usage in CIs) 8 | # If you develop for this project, please use `make cmake-format`. 9 | ############################################################################### 10 | 11 | cd "$( cd "$(dirname "$0")"; pwd -P )/.." 12 | source tools/utils.sh 13 | 14 | print_info "Run cmake-format on all CMake files" 15 | find . -type f \ 16 | ! -path "./build/*" \ 17 | \( -name "CMakeLists.txt" -o -name "*.cmake" \) \ 18 | -exec cmake-format -c .cmake-format -i {} + 19 | -------------------------------------------------------------------------------- /tools/run_cppcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | ############################################################################### 7 | # Run cppcheck on all sources (for usage in CIs) 8 | # If you develop for this project, please use `make cppcheck`. 9 | ############################################################################### 10 | 11 | cd "$( cd "$(dirname "$0")"; pwd -P )/.." 12 | source tools/utils.sh 13 | 14 | print_info "Run cppcheck on all source files" 15 | cppcheck --enable=all --error-exitcode=1 -Isrc -j2 ./src 16 | -------------------------------------------------------------------------------- /tools/run_shellcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | ############################################################################### 7 | # Run shellcheck on all bash script (for usage in CIs) 8 | # This is a convenience script that excludes some warnings. 9 | ############################################################################### 10 | 11 | cd "$(dirname "$0")/.." 12 | source tools/utils.sh 13 | 14 | print_important "Run shellcheck on all source files" 15 | 16 | find . ! -path "./build/*" -type f -name "*.sh" \ 17 | -exec shellcheck -x \ 18 | -e SC1090,SC2143,SC1091,SC2010,SC2016 \ 19 | {} \+ 20 | 21 | # SC1090: Can't follow non-constant source. Use a directive to specify location 22 | # SC2143: Use grep -q instead of comparing output with 23 | # SC1091: Not following 24 | # SC2010: Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames. 25 | # SC2016: Expressions don't expand in single quotes, use double quotes for that. 26 | 27 | print_success "No issues found! Great! :-)" 28 | -------------------------------------------------------------------------------- /tools/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################### 4 | # OS infos 5 | OS_NAME="$(uname -s)" 6 | OS_REV="$(uname -r)" 7 | OS_MACH="$(uname -m)" 8 | 9 | if [ "${OS_NAME}" = "Linux" ]; then 10 | JOBS=$(grep -c '^processor' /proc/cpuinfo) 11 | else 12 | JOBS=2 13 | fi 14 | 15 | export OS_NAME 16 | export OS_REV 17 | export OS_MACH 18 | export JOBS 19 | 20 | ########################################################### 21 | # Print 22 | 23 | RED='\033[0;31m' 24 | GREEN='\033[0;32m' 25 | ORANGE='\033[0;33m' 26 | BLUE='\033[0;34m' 27 | LIGHT_BLUE='\033[1;34m' 28 | NC='\033[0m' # No Color 29 | 30 | print_important() { 31 | printf '%b' "${BLUE}${1}${NC}\n" 32 | } 33 | 34 | print_success() { 35 | printf '%b' "${GREEN}${1}${NC}\n" 1>&2 36 | } 37 | 38 | print_info() { 39 | printf '%b' "${LIGHT_BLUE}${1}${NC}\n" 40 | } 41 | 42 | print_warning() { 43 | printf '%b' "${ORANGE}${1}${NC}\n" 44 | } 45 | 46 | print_error() { 47 | printf '%b' "${RED}${1}${NC}\n" 1>&2 48 | } 49 | 50 | print_critical() { 51 | printf '%b' "${RED}${1}${NC}\n" 1>&2 52 | exit 1 53 | } 54 | --------------------------------------------------------------------------------