Browse code

Add implementation

Robert Cranston authored on 04/10/2021 20:38:56
Showing 6 changed files

1 1
new file mode 100644
... ...
@@ -0,0 +1,62 @@
1
+---
2
+
3
+WarningsAsErrors: >
4
+    # All,
5
+    *,
6
+
7
+Checks: >
8
+    # All,
9
+    *,
10
+
11
+    # Company/project-specific,
12
+    -google-*,
13
+    -llvm-*,
14
+    -llvmlibc-*,
15
+    -fuchsia-*,
16
+    -altera-*,
17
+
18
+    # Extra command line arguments,
19
+    -clang-diagnostic-unknown-warning-option,
20
+    -clang-diagnostic-ignored-optimization-argument,
21
+
22
+    # "Consider",
23
+    -cppcoreguidelines-avoid-non-const-global-variables,
24
+
25
+    # Style,
26
+    -*-use-trailing-return-type,
27
+    -*-container-size-empty,
28
+    -*-easily-swappable-parameters,
29
+    -*-return-braced-init-list,
30
+    -*-non-private-member-variables-in-classes,
31
+    -*-magic-numbers,
32
+
33
+    # Verbosity,
34
+    -*-braces-around-statements,
35
+    -*-implicit-bool-conversion,
36
+    -*-static-accessed-through-instance,
37
+    -*-named-parameter,
38
+
39
+    # -Weffc++,
40
+    -*-redundant-member-init,
41
+
42
+    # Interaction with C,
43
+    -*-cast*,
44
+    -*-vararg,
45
+    -*array-*decay,
46
+
47
+    # Backwards compatibility,
48
+    -*-union-access,
49
+
50
+    # Static storage duration throwing constructors,
51
+    -cert-err58-cpp,
52
+
53
+    # Complex functions,
54
+    -*readability-function-cognitive-complexity,
55
+
56
+    # Macro magic,
57
+    -*-macro-usage,
58
+    -*bugprone-macro-parentheses,
59
+    -*bugprone-lambda-function-name,
60
+
61
+    # Exceptions,
62
+    -*bugprone-exception-escape,
0 63
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+shadowFunction
0 2
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+/_build
0 2
new file mode 100644
... ...
@@ -0,0 +1,94 @@
1
+# Building
2
+
3
+This project supports [CMake][] and uses [`cmake-common`][]. There are several
4
+ways to use it in other CMake-based projects:
5
+
6
+-   With [`find_package`][]: ([Package][] and) [install][] it on the system.
7
+
8
+-   With [`add_subdirectory`][]: Bundle it.
9
+
10
+-   With [`FetchContent`][]: Download it as part of the CMake configure step.
11
+
12
+-   With [`cmake-common`][]: Use any of the above methods through a simplified
13
+    interface.
14
+
15
+As usual, use [`add_dependencies`][] or [`target_link_libraries`][] (or
16
+`cmake-common`'s `DEPENDENCIES_*`) to declare the dependency.
17
+
18
+[CMake]: https://cmake.org
19
+[`cmake-common`]: https://git.rcrnstn.net/rcrnstn/cmake-common
20
+[`FetchContent`]: https://cmake.org/cmake/help/v3.14/module/FetchContent.html
21
+[`add_subdirectory`]: https://cmake.org/cmake/help/v3.14/command/add_subdirectory.html
22
+[`find_package`]: https://cmake.org/cmake/help/v3.14/command/find_package.html
23
+[Package]: #Package
24
+[Install]: #Install
25
+[`add_dependencies`]: https://cmake.org/cmake/help/v3.14/command/add_dependencies.html
26
+[`target_link_libraries`]: https://cmake.org/cmake/help/v3.14/command/target_link_libraries.html
27
+
28
+## Configure and generate
29
+
30
+To configure and generate a build tree, use [`cmake`][]:
31
+
32
+```sh
33
+cmake -B _build
34
+```
35
+
36
+To set the build type, pass e.g. `-D`[`CMAKE_BUILD_TYPE`][]`=Release`.
37
+
38
+To disable building tests, pass `-D`[`BUILD_TESTING`][]`=OFF`.
39
+
40
+[`cmake`]: https://cmake.org/cmake/help/v3.14/manual/cmake.1.html#generate-a-project-buildsystem
41
+[`CMAKE_BUILD_TYPE`]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_BUILD_TYPE.html
42
+[`BUILD_TESTING`]: https://cmake.org/cmake/help/v3.14/module/CTest.html
43
+
44
+## Build
45
+
46
+To build, use [`cmake --build`][]:
47
+
48
+```sh
49
+cmake --build _build
50
+```
51
+
52
+To build in parallel with native build tools that do not do this by default
53
+(such as [GNU Make][]), pass `--parallel` followed by the number of concurrent
54
+processes to use, e.g. `$(`[`nproc`][]`)`.
55
+
56
+[`cmake --build`]: https://cmake.org/cmake/help/v3.14/manual/cmake.1.html#build-a-project
57
+[GNU Make]: https://www.gnu.org/software/make/
58
+[`nproc`]: https://www.gnu.org/software/coreutils/manual/html_node/nproc-invocation.html
59
+
60
+## Test
61
+
62
+To run tests, use [`ctest`][]:
63
+
64
+```sh
65
+(cd _build && ctest)
66
+```
67
+
68
+To show output from failing tests, pass `--output-on-failure`. To show output
69
+from all tests, pass `--verbose`.
70
+
71
+[`ctest`]: https://cmake.org/cmake/help/v3.14/manual/ctest.1.html
72
+
73
+## Package
74
+
75
+To package, use [`cpack`][]:
76
+
77
+```sh
78
+(cd _build && cpack)
79
+```
80
+
81
+[`cpack`]: https://cmake.org/cmake/help/v3.14/manual/cpack.1.html
82
+
83
+## Install
84
+
85
+To install onto the current system, use [`cmake --install`][]:
86
+
87
+```sh
88
+cmake --install _build
89
+```
90
+
91
+To set the prefix, pass e.g. `-D`[`CMAKE_INSTALL_PREFIX`][]`="$HOME/.local"`.
92
+
93
+[`cmake --install`]: https://cmake.org/cmake/help/v3.14/manual/cmake.1.html#install-a-project
94
+[`CMAKE_INSTALL_PREFIX`]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_INSTALL_PREFIX.html
... ...
@@ -2,6 +2,13 @@
2 2
 
3 3
 A [CMake][]\>=3.14 [C++][] common project helper.
4 4
 
5
+[CMake][] has many useful features but requires verbose per-project
6
+configuration to enable them. This helper allows you to just place your files
7
+where everyone expects them to be anyway, install the tools you want to use,
8
+and things like downloading dependencies, linting, building, testing,
9
+sanitizing, packaging (build artifacts, assets, documentation), and installing
10
+will automatically be enabled.
11
+
5 12
 [`cmake-common`]: https://git.rcrnstn.net/rcrnstn/cmake-common
6 13
 [CMake]: https://cmake.org
7 14
 [C++]: https://en.wikipedia.org/wiki/C++
... ...
@@ -14,11 +21,281 @@ this work is to simply copy the required files or content into your project,
14 21
 optionally(!) mentioning where it came from in the readme or other
15 22
 documentation. See [License][].
16 23
 
24
+Copy the file [`common.cmake`][] into your project and [`include`][] it from
25
+(e.g.) the main [`CMakeLists.txt`][]. This will define a `common` function that
26
+takes a number of arguments to configure the project.
27
+
17 28
 [License]: #license
29
+[`include`]: https://cmake.org/cmake/help/v3.14/command/include.html
30
+[`common.cmake`]: common.cmake
31
+[`CMakeLists.txt`]: https://cmake.org/cmake/help/v3.14/manual/cmake-language.7.html#directories
32
+
33
+### Overview
34
+
35
+```cmake
36
+## CMake
37
+cmake_minimum_required(VERSION 3.14)
38
+if(NOT CMAKE_BUILD_TYPE)
39
+    set(CMAKE_BUILD_TYPE Debug)
40
+endif()
41
+
42
+## Project
43
+project(project-name
44
+    VERSION 1.0.0
45
+    LANGUAGES CXX
46
+)
47
+
48
+## Main target
49
+add_library(${PROJECT_NAME})
50
+
51
+## Common
52
+include(common.cmake)
53
+common(
54
+    CXX_STANDARD 11
55
+    DISABLE_INCLUDE_WHAT_YOU_USE
56
+    PACKAGES
57
+        Package
58
+    EXTERNAL
59
+        external_project
60
+    FETCHCONTENT
61
+        "https://example.com/user/online_project GIT_TAG v1.2.3"
62
+    DEPENDENCIES_PUBLIC
63
+        Package::Package
64
+    DEPENDENCIES_PRIVATE
65
+        external_project
66
+    DEPENDENCIES_TESTS
67
+        online_project
68
+    DEFINITIONS
69
+        PROJECT_FEATURE
70
+)
71
+```
72
+
73
+### Main target
74
+
75
+If there are files in the `src` and/or `include` directories, they are added as
76
+sources to a single "main" target, which is assumed to already be declared with
77
+the name `${`[`PROJECT_NAME`][]`}` (which is automatically set by CMake when
78
+calling [`project`][]). The "main" target may be of any type, such as [normal
79
+executable][], [normal library][], [interface library][], or [custom target][]
80
+(the latter probably with the `ALL` specifier, so that it is added to the
81
+default build target so that it will be run every time).
82
+
83
+[`PUBLIC`][] dependencies are taken from `DEPENDENCIES_PUBLIC` and
84
+[`PRIVATE`][] dependencies are taken from `DEPENDENCIES_PRIVATE`, see
85
+[Dependencies][].
86
+
87
+For [top level project][]s, `*Config.cmake` and `*ConfigVersion.cmake` files
88
+that can be used by [`find_package`][] to find this project are
89
+[`install`][]ed, see [Packaging][].
90
+
91
+[`PROJECT_NAME`]: https://cmake.org/cmake/help/v3.14/variable/PROJECT_NAME.html
92
+[`project`]: https://cmake.org/cmake/help/v3.14/command/project.html
93
+[normal executable]: https://cmake.org/cmake/help/v3.14/command/add_executable.html#normal-executables
94
+[normal library]: https://cmake.org/cmake/help/v3.14/command/add_library.html#normal-libraries
95
+[interface library]: https://cmake.org/cmake/help/v3.14/command/add_library.html#interface-libraries
96
+[custom target]: https://cmake.org/cmake/help/v3.14/command/add_custom_target.html
97
+[`PUBLIC`]: https://cmake.org/cmake/help/v3.14/manual/cmake-buildsystem.7.html#target-usage-requirements
98
+[`PRIVATE`]: https://cmake.org/cmake/help/v3.14/manual/cmake-buildsystem.7.html#target-usage-requirements
99
+[Dependencies]: #dependencies
100
+[top level project]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_PROJECT_NAME.html
101
+[`find_package`]: https://cmake.org/cmake/help/v3.14/command/find_package.html
102
+[`install`]: https://cmake.org/cmake/help/v3.14/command/install.html
103
+[Packaging]: #packaging
104
+
105
+### Test targets
106
+
107
+For [top level project][]s, every file directly under the `tests` directory is
108
+individually added as a source to a separate executable "test" target, which is
109
+automatically declared with the name `${PROJECT_NAME}-test-${TEST_NAME}` and
110
+passed to [`add_test`][]. All files in the `tests/common` directory are also
111
+added as sources to all "test" targets.
112
+
113
+A ([`PRIVATE`][]) dependency on the "main" target is declared and further
114
+([`PRIVATE`][]) dependencies are taken from `DEPENDENCIES_TESTS`, see
115
+[Dependencies][].
116
+
117
+CMake can be run with the standard `-D`[`BUILD_TESTING`][]`=OFF` to disable
118
+testing.
119
+
120
+See [Testing][].
121
+
122
+[`add_test`]: https://cmake.org/cmake/help/v3.14/command/add_test.html
123
+[`BUILD_TESTING`]: https://cmake.org/cmake/help/v3.14/module/CTest.html
124
+[Testing]: #testing
125
+
126
+### Assets
127
+
128
+If there are files in the `assets` directory, a target named
129
+`${PROJECT_NAME}-assets` that runs by default and [symlink][]s them to
130
+`${`[`CMAKE_BINARY_DIR`][]`}/assets` is automatically declared.
131
+
132
+If the path does not include `/tests/`, the asset is [`install`][]ed into
133
+`${`[`CMAKE_INSTALL_DATADIR`][]`}/${CMAKE_PROJECT_NAME}/assets` (note that
134
+`CMAKE_PROJECT_NAME` is the name of the [top level project][]), see
135
+[Packaging][].
136
+
137
+[symlink]: https://en.wikipedia.org/wiki/Symbolic_link
138
+[`CMAKE_BINARY_DIR`]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_BINARY_DIR.html
139
+[`CMAKE_INSTALL_DATADIR`]: https://cmake.org/cmake/help/v3.14/module/GNUInstallDirs.html
140
+
141
+### Documentation
142
+
143
+For [top level project][]s, documentation from the `doc` directory and
144
+repository root is automatically [`install`][]ed, see [Packaging][].
145
+
146
+### Man pages
147
+
148
+For [top level project][]s, [man page][]s from the `man` directory is
149
+automatically [`install`][]ed, see [Packaging][].
150
+
151
+[man page]: https://en.wikipedia.org/wiki/Man_page
152
+
153
+### Dependencies
154
+
155
+Dependencies on other targets are declared with `DEPENDENCIES_PUBLIC`,
156
+`DEPENDENCIES_PRIVATE`, and `DEPENDENCIES_TESTS`, see [Main target][] and [Test
157
+targets][].
158
+
159
+Three mechanisms to make these dependencies available are provided, given as
160
+arguments to the `common` function:
161
+
162
+-   `PACKAGES`: Packages passed to [`find_package`][]`(...)` in order to find
163
+    system packages.
164
+-   `EXTERNAL`: Subdirectories of `external` that is passed to
165
+    [`add_subdirectory`][].
166
+-   `FETCHCONTENT`: [Git][] [URL][]s passed to [`FetchContent_Declare`][]'s
167
+    `GIT_REPOSITORY`. If a specific [Git tag][] is wanted, specify it in the
168
+    same (quoted) argument, delimited with `GIT_TAG`, see [Usage][].
169
+    (`GIT_SHALLOW` and `GIT_PROGRESS` are always set to `TRUE`).
170
+    [`FetchContent_MakeAvailable`][] is automatically called in order to
171
+    download and make targets available during the CMake configure step.
172
+
173
+If more involved steps are required to make targets available, perform them
174
+manually before calling the `common` function.
175
+
176
+[Main target]: #main-target
177
+[Test targets]: #test-targets
178
+[`add_subdirectory`]: https://cmake.org/cmake/help/v3.14/command/add_subdirectory.html
179
+[Git]: https://git-scm.com
180
+[URL]: https://en.wikipedia.org/wiki/URL
181
+[`FetchContent_Declare`]: https://cmake.org/cmake/help/v3.14/module/FetchContent.html#command:fetchcontent_declare
182
+[`FetchContent_MakeAvailable`]: https://cmake.org/cmake/help/v3.14/module/FetchContent.html#command:fetchcontent_makeavailable
183
+[Git tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging
184
+[Usage]: #usage
185
+
186
+### Preprocessor definitions
187
+
188
+[Preprocessor definitions][] can be specified with the `DEFINITIONS` argument
189
+to the `common` function. Both `NAME` and `NAME=EXPANSION` forms are supported.
190
+
191
+[preprocessor definitions]: https://en.cppreference.com/w/cpp/preprocessor/replace#.23define_directives
192
+
193
+### Language version
194
+
195
+For [top level project][]s, the `CXX_STANDARD` argument to the `common`
196
+function sets the [`CXX_STANDARD`][] property applied to configured targets
197
+([`CXX_STANDARD_REQUIRED`][] is always set to `ON`, [`CXX_EXTENSIONS`][] is
198
+always set to `OFF`).
199
+
200
+[`CXX_STANDARD`]: https://cmake.org/cmake/help/v3.14/prop_tgt/CXX_STANDARD.html
201
+[`CXX_STANDARD_REQUIRED`]: https://cmake.org/cmake/help/v3.14/prop_tgt/CXX_STANDARD_REQUIRED.html
202
+[`CXX_EXTENSIONS`]: https://cmake.org/cmake/help/v3.14/prop_tgt/CXX_EXTENSIONS.html
203
+
204
+### Build options
205
+
206
+For [top level project][]s, aggressive warnings are enabled and treated as
207
+errors.
208
+
209
+For projects compiled with [GCC][] or [Clang][], [`-Wshadow`][] is enabled by
210
+default, but can be disabled by passing `DISABLE_WSHADOW` to the `common`
211
+function.
212
+
213
+For projects compiled with [MSVC][], [`/Zc:__cplusplus`][] is enabled.
214
+
215
+If supported, [interprocedural optimization][] (also known as link-time
216
+optimization) is enabled.
217
+
218
+For projects compiled with the `Debug` [build type][] with [GCC][] or
219
+[Clang][], [address sanitizer][] ([wiki][address sanitizer wiki]), [leak
220
+sanitizer][] ([wiki][leak sanitizer wiki]), and [undefined behavior
221
+sanitizer][] are linked into all targets. The sanitizers can be disabled by
222
+passing `DISABLE_SANITIZERS` to the `common` function. Suppression files that
223
+match the [glob][]s `*.asan.supp`, `*.lsan.supp`, and `*.ubsan.supp`
224
+respectively in the repository root are automatically used.
225
+
226
+[GCC]: https://gcc.gnu.org
227
+[Clang]: https://clang.llvm.org
228
+[`-Wshadow=`]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wshadow
229
+[MSVC]: https://visualstudio.microsoft.com/vs/
230
+[`/Zc:__cplusplus`]: https://learn.microsoft.com/en-us/cpp/build/reference/zc-cplusplus
231
+[interprocedural optimization]: https://en.wikipedia.org/wiki/Interprocedural_optimization
232
+[build type]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_BUILD_TYPE.html
233
+[address sanitizer]: https://clang.llvm.org/docs/AddressSanitizer.html
234
+[address sanitizer wiki]: https://github.com/google/sanitizers/wiki/AddressSanitizer
235
+[leak sanitizer]: https://clang.llvm.org/docs/LeakSanitizer.html
236
+[leak sanitizer wiki]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer
237
+[undefined behavior sanitizer]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
238
+[glob]: https://en.wikipedia.org/wiki/Glob_(programming)
239
+
240
+### Tools
241
+
242
+For [top level project][]s compiled with the `Debug` [build type][],
243
+[Cppcheck][], [Clang-Tidy][], and [Include What You Use][] are automatically
244
+configured if present on the system. The tools can be disabled by passing the
245
+`DISABLE_CPPCHECK`, `DISABLE_CLANG_TIDY`, and/or `DISABLE_INCLUDE_WHAT_YOU_USE`
246
+options, respectively, to the `common` function. CppCheck suppressions list
247
+files that match the [glob][] `.cppcheck.supp`, a Clang-Tidy configuration file
248
+named `.clang-tidy`, and Include What You Use [mapping files][] that match the
249
+[glob][] `*.iwyu.imp`, in the repository root, are automatically used.
250
+
251
+[Cppcheck]: https://cppcheck.sourceforge.io
252
+[Clang-Tidy]: https://clang.llvm.org/extra/clang-tidy
253
+[Include What You Use]: https://include-what-you-use.org
254
+[mapping files]: https://github.com/include-what-you-use/include-what-you-use/blob/0.20/docs/IWYUMappings.md
255
+
256
+### Testing
257
+
258
+For [top level project][]s, [CTest][] is automatically configured and can be
259
+run with [`ctest`][], see the [`BUILDING.md`][] auxiliary file.
260
+
261
+[CTest]: https://cmake.org/cmake/help/v3.14/module/CTest.html
262
+[`ctest`]: https://cmake.org/cmake/help/v3.14/manual/ctest.1.html
263
+[`BUILDING.md`]: BUILDING.md
264
+
265
+### Packaging
266
+
267
+For [top level project][]s, [CPack][] is automatically configured and can be
268
+run with [`cpack`][], see the [`BUILDING.md`][] auxiliary file.
269
+
270
+[CPack]: https://cmake.org/cmake/help/v3.14/module/Pack.html
271
+[`cpack`]: ttps://cmake.org/cmake/help/v3.14/manual/cpack.1.html
272
+
273
+## Auxiliary files
274
+
275
+This repository includes some files besides [`common.cmake`][] that may be of
276
+use:
277
+
278
+-   [`BUILDING.md`][]: Instructions to build a project that uses
279
+    [`cmake-common`][].
280
+
281
+-   [`.gitignore`][]: A configuration file for [Git][] that makes it ignore the
282
+    created `_build` directory created when following the instructions in
283
+    [`BUILDING.md`][].
284
+
285
+-   [`.cppcheck.supp`][]: A suppressions list for [CppCheck][] with a set of
286
+    suppressed warnings.
287
+
288
+-   [`.clang-tidy`][]: A configuration file for [Clang-Tidy][] with a set of
289
+    enabled and disabled warnings.
290
+
291
+[`.gitignore`]: .gitignore
292
+[`.cppcheck.supp`]: .cppcheck.supp
293
+[`.clang-tidy`]: .clang-tidy
18 294
 
19 295
 ## License
20 296
 
21 297
 Licensed under the [BSD 0-Clause License][] unless otherwise noted, see the
22
-[`LICENSE`](LICENSE) file.
298
+[`LICENSE`][] file.
23 299
 
24 300
 [BSD 0-Clause License]: https://choosealicense.com/licenses/0bsd/
301
+[`LICENSE`]: LICENSE
25 302
new file mode 100644
... ...
@@ -0,0 +1,427 @@
1
+# https://git.rcrnstn.net/rcrnstn/cmake-common
2
+
3
+## CMake modules
4
+include(FetchContent)
5
+include(CMakePackageConfigHelpers)
6
+include(CheckIPOSupported)
7
+include(GNUInstallDirs)
8
+include(CTest)
9
+if(NOT CPack_CMake_INCLUDED)
10
+    include(CPack)
11
+endif()
12
+
13
+function(common)
14
+    ## Arguments
15
+    set(OPTIONS
16
+        DISABLE_WSHADOW
17
+        DISABLE_SANITIZERS
18
+        DISABLE_CPPCHECK
19
+        DISABLE_CLANG_TIDY
20
+        DISABLE_INCLUDE_WHAT_YOU_USE
21
+    )
22
+    set(ONE_VALUE_ARGS
23
+        CXX_STANDARD
24
+    )
25
+    set(MULTI_VALUE_ARGS
26
+        PACKAGES
27
+        EXTERNAL
28
+        FETCHCONTENT
29
+        DEPENDENCIES_PUBLIC
30
+        DEPENDENCIES_PRIVATE
31
+        DEPENDENCIES_TESTS
32
+        DEFINITIONS
33
+    )
34
+    cmake_parse_arguments(PARSE_ARGV 0 ARG
35
+        "${OPTIONS}"
36
+        "${ONE_VALUE_ARGS}"
37
+        "${MULTI_VALUE_ARGS}"
38
+    )
39
+
40
+    ## Variables
41
+    set(PROJECT_IS_TOP_LEVEL)
42
+    set(PROJECT_IS_INTERFACE_LIBRARY)
43
+    set(PROJECT_IS_DEBUG)
44
+    get_target_property(PROJECT_TYPE ${PROJECT_NAME} TYPE)
45
+    string(TOLOWER "${CMAKE_BUILD_TYPE}" PROJECT_BUILD_TYPE)
46
+    if("${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}")
47
+        set(PROJECT_IS_TOP_LEVEL TRUE)
48
+    endif()
49
+    if("${PROJECT_TYPE}" STREQUAL "INTERFACE_LIBRARY")
50
+        set(PROJECT_IS_INTERFACE_LIBRARY TRUE)
51
+    endif()
52
+    if("${PROJECT_BUILD_TYPE}" STREQUAL "debug")
53
+        set(PROJECT_IS_DEBUG TRUE)
54
+    endif()
55
+
56
+    ## Packages
57
+    foreach(PACKAGE ${ARG_PACKAGES})
58
+        find_package(${PACKAGE})
59
+    endforeach()
60
+
61
+    ## External
62
+    foreach(EXTERNAL ${ARG_EXTERNAL})
63
+        add_subdirectory(external/${EXTERNAL} EXCLUDE_FROM_ALL)
64
+    endforeach()
65
+
66
+    ## FetchContent
67
+    foreach(FETCHCONTENT ${ARG_FETCHCONTENT})
68
+        get_filename_component(FETCHCONTENT_NAME ${FETCHCONTENT} NAME)
69
+        FetchContent_Declare(${FETCHCONTENT_NAME}
70
+            GIT_REPOSITORY ${FETCHCONTENT}
71
+            GIT_SHALLOW TRUE
72
+            GIT_PROGRESS TRUE
73
+        )
74
+        FetchContent_MakeAvailable(${FETCHCONTENT_NAME})
75
+    endforeach()
76
+
77
+    ## Main target
78
+    target_compile_definitions(${PROJECT_NAME} PUBLIC ${ARG_DEFINITIONS})
79
+    file(GLOB_RECURSE SRC CONFIGURE_DEPENDS
80
+        RELATIVE "${PROJECT_SOURCE_DIR}"
81
+        src/*
82
+    )
83
+    file(GLOB_RECURSE INCLUDE CONFIGURE_DEPENDS
84
+        RELATIVE "${PROJECT_SOURCE_DIR}"
85
+        include/*
86
+    )
87
+    if(NOT ("${SRC}" STREQUAL "" AND "${INCLUDE}" STREQUAL ""))
88
+        if(PROJECT_IS_INTERFACE_LIBRARY)
89
+            target_include_directories(${PROJECT_NAME}
90
+                INTERFACE
91
+                    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
92
+                    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
93
+            )
94
+        else()
95
+            target_sources(${PROJECT_NAME}
96
+                PRIVATE
97
+                    ${SRC}
98
+                    ${INCLUDE}
99
+            )
100
+            target_include_directories(${PROJECT_NAME}
101
+                PUBLIC
102
+                    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
103
+                    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
104
+                PRIVATE
105
+                    src
106
+            )
107
+            target_link_libraries(${PROJECT_NAME}
108
+                PUBLIC
109
+                    ${ARG_DEPENDENCIES_PUBLIC}
110
+                PRIVATE
111
+                    "$<BUILD_INTERFACE:${ARG_DEPENDENCIES_PRIVATE}>"
112
+            )
113
+        endif()
114
+        if(PROJECT_IS_TOP_LEVEL)
115
+            install(
116
+                DIRECTORY
117
+                    include/
118
+                DESTINATION
119
+                    ${CMAKE_INSTALL_INCLUDEDIR}
120
+            )
121
+            install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME})
122
+            install(EXPORT ${PROJECT_NAME}
123
+                FILE ${PROJECT_NAME}Config.cmake
124
+                DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
125
+            )
126
+            write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake
127
+                COMPATIBILITY SameMajorVersion
128
+            )
129
+            install(
130
+                FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
131
+                DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
132
+            )
133
+        endif()
134
+    endif()
135
+
136
+    ## Test targets
137
+    set(TEST_TARGETS)
138
+    if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING)
139
+        file(GLOB_RECURSE TESTS_COMMON CONFIGURE_DEPENDS
140
+            tests/common/*
141
+        )
142
+        file(GLOB TESTS CONFIGURE_DEPENDS
143
+            LIST_DIRECTORIES FALSE
144
+            tests/*
145
+        )
146
+        foreach(TEST ${TESTS})
147
+            get_filename_component(TEST_NAME "${TEST}" NAME_WE)
148
+            set(TEST_TARGET "${PROJECT_NAME}-test-${TEST_NAME}")
149
+            add_executable(${TEST_TARGET}
150
+                ${TEST}
151
+                ${TESTS_COMMON}
152
+            )
153
+            target_include_directories(${TEST_TARGET}
154
+                PRIVATE
155
+                    tests
156
+            )
157
+            target_link_libraries(${TEST_TARGET}
158
+                PRIVATE
159
+                    ${PROJECT_NAME}
160
+                    ${ARG_DEPENDENCIES_TESTS}
161
+            )
162
+            add_test(
163
+                NAME    ${TEST_TARGET}
164
+                COMMAND ${TEST_TARGET}
165
+            )
166
+            list(APPEND TEST_TARGETS ${TEST_TARGET})
167
+        endforeach()
168
+    endif()
169
+
170
+    ## Build targets
171
+    set(BUILD_TARGETS ${TEST_TARGETS})
172
+    if(NOT PROJECT_IS_INTERFACE_LIBRARY)
173
+        list(APPEND BUILD_TARGETS ${PROJECT_NAME})
174
+    endif()
175
+
176
+    ## Assets
177
+    file(REMOVE_RECURSE
178
+        ${CMAKE_BINARY_DIR}/assets
179
+    )
180
+    file(GLOB_RECURSE ASSETS CONFIGURE_DEPENDS
181
+        RELATIVE "${PROJECT_SOURCE_DIR}"
182
+        assets/*
183
+    )
184
+    if(NOT "${ASSETS}" STREQUAL "")
185
+        add_custom_target(${PROJECT_NAME}-assets)
186
+        add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-assets)
187
+    endif()
188
+    foreach(ASSET ${ASSETS})
189
+        get_filename_component(ASSET_DIR ${ASSET} DIRECTORY)
190
+        if("${ASSET}" MATCHES "/tests/")
191
+            set(ASSET_TEST TRUE)
192
+        else()
193
+            set(ASSET_TEST FALSE)
194
+        endif()
195
+        if(ASSET_TEST AND NOT PROJECT_IS_TOP_LEVEL)
196
+            continue()
197
+        endif()
198
+        add_custom_command(
199
+            DEPENDS
200
+                ${PROJECT_SOURCE_DIR}/${ASSET}
201
+            OUTPUT
202
+                ${CMAKE_BINARY_DIR}/${ASSET}
203
+            COMMAND
204
+                ${CMAKE_COMMAND} -E make_directory
205
+                    ${CMAKE_BINARY_DIR}/${ASSET_DIR}
206
+            COMMAND
207
+                ${CMAKE_COMMAND} -E create_symlink
208
+                    ${PROJECT_SOURCE_DIR}/${ASSET}
209
+                    ${CMAKE_BINARY_DIR}/${ASSET}
210
+            VERBATIM
211
+        )
212
+        set_property(TARGET ${PROJECT_NAME}-assets APPEND PROPERTY
213
+            SOURCES ${CMAKE_BINARY_DIR}/${ASSET}
214
+        )
215
+        if(NOT ASSET_TEST)
216
+            install(
217
+                FILES
218
+                    ${PROJECT_SOURCE_DIR}/${ASSET}
219
+                DESTINATION
220
+                    ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}/${ASSET_DIR}
221
+            )
222
+        endif()
223
+    endforeach()
224
+
225
+    ## Documentation
226
+    if(PROJECT_IS_TOP_LEVEL)
227
+        file(GLOB_RECURSE DOCS CONFIGURE_DEPENDS
228
+            RELATIVE "${PROJECT_SOURCE_DIR}"
229
+            doc/*
230
+        )
231
+        file(GLOB DOCS_ROOT CONFIGURE_DEPENDS
232
+            RELATIVE "${PROJECT_SOURCE_DIR}"
233
+            README*
234
+            LICENSE*
235
+            COPYING*
236
+            CHANGELOG*
237
+            CHANGES*
238
+            HISTORY*
239
+            NEWS*
240
+            RELEASES*
241
+            AUTHORS*
242
+            ACKNOWLEDGMENTS*
243
+            CONTRIBUTORS*
244
+            CONTRIBUTING*
245
+            CODE_OF_CONDUCT*
246
+            SECURITY*
247
+            SUPPORT*
248
+        )
249
+        list(APPEND DOCS ${DOCS_ROOT})
250
+        foreach(DOC ${DOCS})
251
+            get_filename_component(DOC_DIR ${DOC} DIRECTORY)
252
+            install(
253
+                FILES
254
+                    ${PROJECT_SOURCE_DIR}/${DOC}
255
+                DESTINATION
256
+                    ${CMAKE_INSTALL_DOCDIR}/${DOC_DIR}
257
+            )
258
+        endforeach()
259
+    endif()
260
+
261
+    ## Man pages
262
+    if(PROJECT_IS_TOP_LEVEL)
263
+        file(GLOB_RECURSE MANS CONFIGURE_DEPENDS
264
+            RELATIVE "${PROJECT_SOURCE_DIR}"
265
+            man/*
266
+        )
267
+        foreach(MAN ${MANS})
268
+            get_filename_component(MAN_DIR ${MAN} DIRECTORY)
269
+            install(
270
+                FILES
271
+                    ${PROJECT_SOURCE_DIR}/${MAN}
272
+                DESTINATION
273
+                    ${CMAKE_INSTALL_MANDIR}/${MAN_DIR}
274
+            )
275
+        endforeach()
276
+    endif()
277
+
278
+    ## Language version
279
+    set_target_properties(${BUILD_TARGETS} PROPERTIES
280
+        CXX_STANDARD          "${ARG_CXX_STANDARD}"
281
+        CXX_STANDARD_REQUIRED ON
282
+        CXX_EXTENSIONS        OFF
283
+    )
284
+
285
+    ## Build options
286
+    if(PROJECT_IS_TOP_LEVEL)
287
+        if(MSVC)
288
+            set(MSVC_OPTIONS)
289
+            if(MSVC_VERSION GREATER_EQUAL 1914)
290
+                set(MSVC_OPTIONS "/Zc:__cplusplus")
291
+            endif()
292
+            set(COMPILE_OPTIONS
293
+                /permissive-
294
+                /WX
295
+                /W4
296
+                ${MSVC_OPTIONS}
297
+            )
298
+        elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|.*Clang")
299
+            set(SANITIZE_OPTIONS)
300
+            if(PROJECT_IS_DEBUG AND NOT ARG_DISABLE_SANITIZERS)
301
+                set(SANITIZE_OPTIONS
302
+                    $<$<CONFIG:Debug>:
303
+                        -fno-omit-frame-pointer
304
+                        -fsanitize=address,leak,undefined
305
+                        -fsanitize=float-divide-by-zero
306
+                        -fsanitize=float-cast-overflow
307
+                        # -fsanitize=pointer-compare
308
+                        # -fsanitize=pointer-subtract
309
+                        # -fsanitize=implicit-conversion
310
+                        -fno-sanitize-recover=all
311
+                    >
312
+                )
313
+            endif()
314
+            set(WSHADOW_OPTION)
315
+            if(NOT ARG_DISABLE_WSHADOW)
316
+                set(WSHADOW_OPTION -Wshadow)
317
+            endif()
318
+            set(COMPILE_OPTIONS
319
+                -pedantic
320
+                -Werror # -Wfatal-errors
321
+                -Wall -Wextra
322
+                -Wno-missing-braces -Wmissing-field-initializers
323
+                -Wconversion -Wsign-conversion
324
+                -Wdouble-promotion
325
+                -Wimplicit-fallthrough
326
+                -Wvla
327
+                -Wzero-as-null-pointer-constant
328
+                -Weffc++
329
+                ${WSHADOW_OPTION}
330
+                ${SANITIZE_OPTIONS}
331
+            )
332
+            set(LINK_OPTIONS
333
+                ${SANITIZE_OPTIONS}
334
+            )
335
+            check_ipo_supported(RESULT INTERPROCEDURAL_OPTIMIZATION)
336
+        endif()
337
+        set_property(TARGET ${BUILD_TARGETS} APPEND PROPERTY
338
+            COMPILE_OPTIONS "${COMPILE_OPTIONS}"
339
+        )
340
+        set_property(TARGET ${BUILD_TARGETS} APPEND PROPERTY
341
+            LINK_OPTIONS "${LINK_OPTIONS}"
342
+        )
343
+        set_property(TARGET ${BUILD_TARGETS} APPEND PROPERTY
344
+            INTERPROCEDURAL_OPTIMIZATION "${INTERPROCEDURAL_OPTIMIZATION}"
345
+        )
346
+    endif()
347
+
348
+    ## Sanitizer environment variables
349
+    set(SAN_STRIP_PATH_PREFIX strip_path_prefix=${PROJECT_BINARY_DIR})
350
+    set(ASAN_OPTIONS  "${SAN_STRIP_PATH_PREFIX}")
351
+    set(LSAN_OPTIONS  "${SAN_STRIP_PATH_PREFIX}")
352
+    set(UBSAN_OPTIONS "${SAN_STRIP_PATH_PREFIX}")
353
+    file(GLOB ASAN_SUPPS  CONFIGURE_DEPENDS *.asan.supp)
354
+    file(GLOB LSAN_SUPPS  CONFIGURE_DEPENDS *.lsan.supp)
355
+    file(GLOB UBSAN_SUPPS CONFIGURE_DEPENDS *.ubsan.supp)
356
+    foreach(ASAN_SUPP ${ASAN_SUPPS})
357
+        set(ASAN_OPTIONS "${ASAN_OPTIONS},suppressions=${ASAN_SUPP}")
358
+    endforeach()
359
+    foreach(LSAN_SUPP ${LSAN_SUPPS})
360
+        set(LSAN_OPTIONS "${LSAN_OPTIONS},suppressions=${LSAN_SUPP}")
361
+    endforeach()
362
+    foreach(UBSAN_SUPP ${UBSAN_SUPPS})
363
+        set(UBSAN_OPTIONS "${UBSAN_OPTIONS},suppressions=${UBSAN_SUPP}")
364
+    endforeach()
365
+    # set(ASAN_OPTIONS "${ASAN_OPTIONS},detect_leaks=1")
366
+    # set(UBSAN_OPTIONS "${UBSAN_OPTIONS},print_stacktrace=1")
367
+    set_property(TEST ${TEST_TARGETS} APPEND PROPERTY
368
+        ENVIRONMENT
369
+            ASAN_OPTIONS=${ASAN_OPTIONS}
370
+            LSAN_OPTIONS=${LSAN_OPTIONS}
371
+            UBSAN_OPTIONS=${UBSAN_OPTIONS}
372
+    )
373
+
374
+    ## Tools
375
+    if(PROJECT_IS_TOP_LEVEL AND PROJECT_IS_DEBUG)
376
+        find_program(CPPCHECK             cppcheck)
377
+        find_program(CLANG_TIDY           clang-tidy)
378
+        find_program(INCLUDE_WHAT_YOU_USE include-what-you-use)
379
+        set(CPPCHECK_OPTIONS)
380
+        set(IWYU_OPTIONS)
381
+        file(GLOB CPPCHECK_SUPPS CONFIGURE_DEPENDS *.cppcheck.supp)
382
+        file(GLOB IWYU_IMPS      CONFIGURE_DEPENDS *.iwyu.imp)
383
+        if("${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang")
384
+            set(CPPCHECK_OPTIONS --clang)
385
+        endif()
386
+        foreach(CPPCHECK_SUPP ${CPPCHECK_SUPPS})
387
+            set(CPPCHECK_OPTIONS "${CPPCHECK_OPTIONS}"
388
+                "--suppressions-list=${CPPCHECK_SUPP}"
389
+            )
390
+        endforeach()
391
+        foreach(IWYU_IMP ${IWYU_IMPS})
392
+            set(IWYU_OPTIONS "${IWYU_OPTIONS}"
393
+                "-Xiwyu" "--mapping_file=${IWYU_IMP}"
394
+            )
395
+        endforeach()
396
+        if(CPPCHECK AND NOT ARG_DISABLE_CPPCHECK)
397
+            set(CXX_CPPCHECK ${CPPCHECK}
398
+                --error-exitcode=1
399
+                --enable=warning,style
400
+                --inline-suppr
401
+                --template=gcc
402
+                "--cppcheck-build-dir=${CMAKE_BINARY_DIR}/cppcheck"
403
+                "--std=c++${ARG_CXX_STANDARD}"
404
+                ${CPPCHECK_OPTIONS}
405
+            )
406
+        endif()
407
+        if(CLANG_TIDY AND NOT ARG_DISABLE_CLANG_TIDY)
408
+            set(CXX_CLANG_TIDY ${CLANG_TIDY}
409
+                --extra-arg=-Wno-unknown-warning-option
410
+                --extra-arg=-Wno-ignored-optimization-argument
411
+            )
412
+        endif()
413
+        if(INCLUDE_WHAT_YOU_USE AND NOT ARG_DISABLE_INCLUDE_WHAT_YOU_USE)
414
+            set(CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}
415
+                -Wno-unknown-warning-option
416
+                -Wno-ignored-optimization-argument
417
+                ${IWYU_OPTIONS}
418
+            )
419
+        endif()
420
+        set_target_properties(${BUILD_TARGETS} PROPERTIES
421
+            EXPORT_COMPILE_COMMANDS  ON
422
+            CXX_CPPCHECK             "${CXX_CPPCHECK}"
423
+            CXX_CLANG_TIDY           "${CXX_CLANG_TIDY}"
424
+            CXX_INCLUDE_WHAT_YOU_USE "${CXX_INCLUDE_WHAT_YOU_USE}"
425
+        )
426
+    endif()
427
+endfunction()