Browse code

Add implementation

Robert Cranston authored on 04/10/2021 20:38:56
Showing 1 changed files
1 1
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()