Browse code

Add project

Robert Cranston authored on 22/12/2021 10:42:11
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,310 @@
1
+# https://git.rcrnstn.net/rcrnstn/cmake-cxx
2
+
3
+function(cxx)
4
+    ## Arguments
5
+    set(OPTIONS
6
+        DISABLE_SANITIZERS
7
+        DISABLE_CPPCHECK
8
+        DISABLE_CLANG_TIDY
9
+        DISABLE_INCLUDE_WHAT_YOU_USE
10
+    )
11
+    set(ONE_VALUE_ARGS
12
+        CXX_STANDARD
13
+    )
14
+    set(MULTI_VALUE_ARGS
15
+        PACKAGES
16
+        FETCHCONTENT
17
+        DEPENDENCIES_PUBLIC
18
+        DEPENDENCIES_PRIVATE
19
+        DEPENDENCIES_TESTS
20
+        DEFINES
21
+    )
22
+    cmake_parse_arguments(PARSE_ARGV 0 ARG
23
+        "${OPTIONS}"
24
+        "${ONE_VALUE_ARGS}"
25
+        "${MULTI_VALUE_ARGS}"
26
+    )
27
+
28
+    ## Helper variables
29
+    set(NON_INTERFACE_TARGETS)
30
+    set(PROJECT_IS_INTERFACE_LIBRARY)
31
+    set(PROJECT_IS_TOP_LEVEL)
32
+    get_target_property(PROJECT_TYPE ${PROJECT_NAME} TYPE)
33
+    if("${PROJECT_TYPE}" STREQUAL "INTERFACE_LIBRARY")
34
+        set(PROJECT_IS_INTERFACE_LIBRARY TRUE)
35
+    endif()
36
+    if("${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}")
37
+        set(PROJECT_IS_TOP_LEVEL TRUE)
38
+    endif()
39
+
40
+    ## CMake modules
41
+    include(FetchContent)
42
+    include(CMakePackageConfigHelpers)
43
+    include(CheckIPOSupported)
44
+    include(GNUInstallDirs)
45
+    if(PROJECT_IS_TOP_LEVEL)
46
+        include(CTest)
47
+        include(CPack)
48
+    endif()
49
+
50
+    ## Packages
51
+    foreach(PACKAGE ${ARG_PACKAGES})
52
+        find_package(${PACKAGE} REQUIRED)
53
+    endforeach()
54
+
55
+    ## Fetch content
56
+    foreach(FETCHCONTENT ${ARG_FETCHCONTENT})
57
+        get_filename_component(FETCHCONTENT_NAME ${FETCHCONTENT} NAME)
58
+        FetchContent_Declare(${FETCHCONTENT_NAME}
59
+            GIT_REPOSITORY ${FETCHCONTENT}
60
+        )
61
+        FetchContent_MakeAvailable(${FETCHCONTENT_NAME})
62
+    endforeach()
63
+
64
+    ## Configure main target
65
+    file(GLOB_RECURSE SRC CONFIGURE_DEPENDS
66
+        src/*
67
+    )
68
+    file(GLOB_RECURSE INCLUDE CONFIGURE_DEPENDS
69
+        include/*
70
+    )
71
+    if(NOT ("${SRC}" STREQUAL "" AND "${INCLUDE}" STREQUAL ""))
72
+        target_sources(${PROJECT_NAME}
73
+            PRIVATE
74
+                ${SRC}
75
+                ${INCLUDE}
76
+        )
77
+        set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY
78
+            PUBLIC_HEADER ${INCLUDE}
79
+        )
80
+        if(PROJECT_IS_INTERFACE_LIBRARY)
81
+            target_include_directories(${PROJECT_NAME}
82
+                INTERFACE
83
+                    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
84
+                    "$<INSTALL_INTERFACE:include>"
85
+            )
86
+        else()
87
+            target_include_directories(${PROJECT_NAME}
88
+                PUBLIC
89
+                    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
90
+                    "$<INSTALL_INTERFACE:include>"
91
+                PRIVATE
92
+                    src
93
+            )
94
+            list(APPEND NON_INTERFACE_TARGETS ${PROJECT_NAME})
95
+        endif()
96
+        target_link_libraries(${PROJECT_NAME}
97
+            PUBLIC
98
+                ${ARG_DEPENDENCIES_PUBLIC}
99
+            PRIVATE
100
+                "$<BUILD_INTERFACE:${ARG_DEPENDENCIES_PRIVATE}>"
101
+        )
102
+        if(PROJECT_IS_TOP_LEVEL)
103
+            install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME})
104
+            install(EXPORT ${PROJECT_NAME}
105
+                FILE ${PROJECT_NAME}Config.cmake
106
+                DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
107
+            )
108
+            write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake
109
+                COMPATIBILITY SameMajorVersion
110
+            )
111
+            install(
112
+                FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
113
+                DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
114
+            )
115
+        endif()
116
+    endif()
117
+
118
+    ## Declare and configure test targets
119
+    if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING)
120
+        file(GLOB_RECURSE TESTS_COMMON CONFIGURE_DEPENDS
121
+            tests/common/*
122
+        )
123
+        file(GLOB TESTS LIST_DIRECTORIES FALSE CONFIGURE_DEPENDS
124
+            tests/*
125
+        )
126
+        foreach(TEST ${TESTS})
127
+            get_filename_component(TEST_NAME "${TEST}" NAME_WE)
128
+            set(TEST_TARGET "${PROJECT_NAME}-test-${TEST_NAME}")
129
+            add_executable(${TEST_TARGET}
130
+                ${TEST}
131
+                ${TESTS_COMMON}
132
+            )
133
+            target_include_directories(${TEST_TARGET}
134
+                PRIVATE
135
+                    tests
136
+            )
137
+            target_link_libraries(${TEST_TARGET}
138
+                PRIVATE
139
+                    ${PROJECT_NAME}
140
+                    ${ARG_DEPENDENCIES_TESTS}
141
+            )
142
+            add_test(
143
+                NAME    ${TEST_TARGET}
144
+                COMMAND ${TEST_TARGET}
145
+            )
146
+            list(APPEND NON_INTERFACE_TARGETS ${TEST_TARGET})
147
+        endforeach()
148
+    endif()
149
+
150
+    ## Declare and configure assets
151
+    file(REMOVE_RECURSE
152
+        ${CMAKE_BINARY_DIR}/assets
153
+    )
154
+    file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}" CONFIGURE_DEPENDS
155
+        assets/*
156
+    )
157
+    if(NOT "${ASSETS}" STREQUAL "")
158
+        add_custom_target(${PROJECT_NAME}-assets)
159
+        add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-assets)
160
+    endif()
161
+    foreach(ASSET ${ASSETS})
162
+        get_filename_component(ASSET_DIR ${ASSET} DIRECTORY)
163
+        add_custom_command(
164
+            DEPENDS
165
+                ${PROJECT_SOURCE_DIR}/${ASSET}
166
+            OUTPUT
167
+                ${CMAKE_BINARY_DIR}/${ASSET}
168
+            COMMAND
169
+                ${CMAKE_COMMAND} -E make_directory
170
+                    ${CMAKE_BINARY_DIR}/${ASSET_DIR}
171
+            COMMAND
172
+                ${CMAKE_COMMAND} -E create_symlink
173
+                    ${PROJECT_SOURCE_DIR}/${ASSET}
174
+                    ${CMAKE_BINARY_DIR}/${ASSET}
175
+            VERBATIM
176
+        )
177
+        set_property(TARGET ${PROJECT_NAME}-assets APPEND PROPERTY
178
+            SOURCES ${CMAKE_BINARY_DIR}/${ASSET}
179
+        )
180
+        if(NOT "${ASSET}" MATCHES "/tests/")
181
+            install(
182
+                FILES
183
+                    ${PROJECT_SOURCE_DIR}/${ASSET}
184
+                DESTINATION
185
+                    ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}/${ASSET_DIR}
186
+            )
187
+        endif()
188
+    endforeach()
189
+
190
+    ## Preprocessor defines
191
+    set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY
192
+        COMPILE_DEFINITIONS ${ARG_DEFINES}
193
+    )
194
+
195
+    ## Language version
196
+    if(NOT "${CXX_STANDARD}" STREQUAL "")
197
+        set_target_properties(${NON_INTERFACE_TARGETS} PROPERTIES
198
+            CXX_STANDARD          ${ARG_CXX_STANDARD}
199
+            CXX_STANDARD_REQUIRED ON
200
+            CXX_EXTENSIONS        OFF
201
+        )
202
+    endif()
203
+
204
+    ## Build options
205
+    if(MSVC)
206
+        set(COMPILE_OPTIONS
207
+            /permissive-
208
+            /WX
209
+            /W4
210
+        )
211
+    elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|.*Clang")
212
+        if(NOT ARG_DISABLE_SANITIZERS)
213
+            set(SANITIZE_OPTIONS
214
+                $<$<CONFIG:Debug>:
215
+                    -g -fno-omit-frame-pointer
216
+                    -fsanitize=address,leak,undefined
217
+                    -fsanitize=float-divide-by-zero
218
+                    -fsanitize=float-cast-overflow
219
+                    # -fsanitize=pointer-compare
220
+                    # -fsanitize=pointer-subtract
221
+                    # -fsanitize=implicit-conversion
222
+                    -fno-sanitize-recover=all
223
+                >
224
+            )
225
+        endif()
226
+        set(COMPILE_OPTIONS
227
+            -pedantic
228
+            -Werror # -Wfatal-errors
229
+            -Wall -Wextra
230
+            -Wconversion -Wsign-conversion
231
+            -Wdouble-promotion
232
+            -Wimplicit-fallthrough
233
+            -Wvla
234
+            # -Wzero-as-null-pointer-constant
235
+            # -Wshadow
236
+            -Weffc++
237
+            ${SANITIZE_OPTIONS}
238
+        )
239
+        set(LINK_OPTIONS
240
+            ${SANITIZE_OPTIONS}
241
+        )
242
+        check_ipo_supported(RESULT INTERPROCEDURAL_OPTIMIZATION)
243
+    endif()
244
+    set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY
245
+        COMPILE_OPTIONS "${COMPILE_OPTIONS}"
246
+    )
247
+    set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY
248
+        LINK_OPTIONS "${LINK_OPTIONS}"
249
+    )
250
+    set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY
251
+        INTERPROCEDURAL_OPTIMIZATION "${INTERPROCEDURAL_OPTIMIZATION}"
252
+    )
253
+
254
+    ## Sanitizer environment variables
255
+    set(SAN_STRIP_PATH_PREFIX strip_path_prefix=${PROJECT_BINARY_DIR})
256
+    set(ASAN_OPTIONS  "${SAN_STRIP_PATH_PREFIX}")
257
+    set(LSAN_OPTIONS  "${SAN_STRIP_PATH_PREFIX}")
258
+    set(UBSAN_OPTIONS "${SAN_STRIP_PATH_PREFIX}")
259
+    file(GLOB ASAN_SUPP CONFIGURE_DEPENDS
260
+        .asan.supp
261
+    )
262
+    file(GLOB LSAN_SUPP CONFIGURE_DEPENDS
263
+        .lsan.supp
264
+    )
265
+    file(GLOB UBSAN_SUPP CONFIGURE_DEPENDS
266
+        .ubsan.supp
267
+    )
268
+    if(NOT "${ASAN_SUPP}" STREQUAL "")
269
+        set(ASAN_OPTIONS "${ASAN_OPTIONS},suppressions=${ASAN_SUPP}")
270
+    endif()
271
+    if(NOT "${LSAN_SUPP}" STREQUAL "")
272
+        set(LSAN_OPTIONS "${LSAN_OPTIONS},suppressions=${LSAN_SUPP}")
273
+    endif()
274
+    if(NOT "${UBSAN_SUPP}" STREQUAL "")
275
+        set(UBSAN_OPTIONS "${UBSAN_OPTIONS},suppressions=${UBSAN_SUPP}")
276
+    endif()
277
+    # set(ASAN_OPTIONS "${ASAN_OPTIONS},detect_leaks=1")
278
+    # set(UBSAN_OPTIONS "${UBSAN_OPTIONS},print_stacktrace=1")
279
+    set_property(TEST ${NON_INTERFACE_TARGETS} APPEND PROPERTY
280
+        ENVIRONMENT
281
+            ASAN_OPTIONS=${ASAN_OPTIONS}
282
+            LSAN_OPTIONS=${LSAN_OPTIONS}
283
+            UBSAN_OPTIONS=${UBSAN_OPTIONS}
284
+    )
285
+
286
+    ## Tools
287
+    if(PROJECT_IS_TOP_LEVEL)
288
+        find_program(CPPCHECK             cppcheck)
289
+        find_program(CLANG_TIDY           clang-tidy)
290
+        find_program(INCLUDE_WHAT_YOU_USE include-what-you-use)
291
+        if(CPPCHECK AND NOT ARG_DISABLE_CPPCHECK)
292
+            set(CXX_CPPCHECK ${CPPCHECK}
293
+                --enable=warning,style
294
+                --error-exitcode=1
295
+            )
296
+        endif()
297
+        if(CLANG_TIDY AND NOT ARG_DISABLE_CLANG_TIDY)
298
+            set(CXX_CLANG_TIDY ${CLANG_TIDY})
299
+        endif()
300
+        if(INCLUDE_WHAT_YOU_USE AND NOT ARG_DISABLE_INCLUDE_WHAT_YOU_USE)
301
+            set(CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE})
302
+        endif()
303
+        set_target_properties(${NON_INTERFACE_TARGETS} PROPERTIES
304
+            EXPORT_COMPILE_COMMANDS  ON
305
+            CXX_CPPCHECK             "${CXX_CPPCHECK}"
306
+            CXX_CLANG_TIDY           "${CXX_CLANG_TIDY}"
307
+            CXX_INCLUDE_WHAT_YOU_USE "${CXX_INCLUDE_WHAT_YOU_USE}"
308
+        )
309
+    endif()
310
+endfunction()