├── .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 | [](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 | [](LICENSE)
3 | [](https://app.travis-ci.com/bugwelle/cpp-starter-project)
4 | [](https://ci.appveyor.com/project/archer96/cpp-starter-project/branch/main)[](https://gitlab.com/bugwelle/cpp-starter-project/pipelines)
5 | [](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 |
--------------------------------------------------------------------------------