File: BasicConfig.cmake

package info (click to toggle)
martchus-cpp-utilities 5.28.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,352 kB
  • sloc: cpp: 12,471; awk: 18; ansic: 12; makefile: 10
file content (754 lines) | stat: -rw-r--r-- 32,723 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)

# check whether the required project meta-data has been set before including this module
if (NOT META_PROJECT_NAME)
    message(FATAL_ERROR "No project name (META_PROJECT_NAME) specified.")
endif ()
if (NOT META_APP_NAME)
    message(FATAL_ERROR "No project name (META_APP_NAME) specified.")
endif ()
if (NOT META_APP_AUTHOR)
    message(FATAL_ERROR "No project name (META_APP_AUTHOR) specified.")
endif ()
if (NOT META_APP_DESCRIPTION)
    message(FATAL_ERROR "No project name (META_APP_DESCRIPTION) specified.")
endif ()

# set project name (displayed in Qt Creator) note: The project name is at least shown in Qt Creator this way but
# unfortunately setting project() from an included file is not sufficient (see
# https://cmake.org/cmake/help/latest/command/project.html#usage).
message(STATUS "Configuring project ${META_PROJECT_NAME}")
project(${META_PROJECT_NAME})

# set META_PROJECT_VARNAME and META_PROJECT_VARNAME_UPPER if not specified explicitly
if (NOT META_PROJECT_VARNAME)
    set(META_PROJECT_VARNAME "${META_PROJECT_NAME}")
endif ()
if (NOT META_PROJECT_VARNAME_UPPER)
    string(TOUPPER ${META_PROJECT_VARNAME} META_PROJECT_VARNAME_UPPER)
endif ()
if (NOT META_PROJECT_VARNAME_LOWER)
    string(REGEX REPLACE "_+" "" META_PROJECT_VARNAME_LOWER "${META_PROJECT_VARNAME}")
    string(TOLOWER "${META_PROJECT_VARNAME_LOWER}" META_PROJECT_VARNAME_LOWER)
endif ()

# allow setting a configuration name to allow installing multiple differently configured versions within the same prefix
# (intended to be used for installing Qt 5 and Qt 6 version or shared and static version within the same prefix)
set(${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_NAME
    ""
    CACHE STRING "sets the configuration name for ${META_PROJECT_NAME}")
set(CONFIGURATION_NAME
    ""
    CACHE STRING "sets the configuration name for all projects within the current build")
set(CONFIGURATION_DISPLAY_NAME
    "${CONFIGURATION_NAME}"
    CACHE STRING
          "sets the display name for the configuration; incorporated in META_APP_NAME; defaults to CONFIGURATION_NAME")
if (${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_NAME STREQUAL "none")
    set(META_CONFIG_NAME "")
elseif (${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_NAME)
    set(META_CONFIG_NAME "${${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_NAME}")
else ()
    set(META_CONFIG_NAME "${CONFIGURATION_NAME}")
endif ()
if (META_CONFIG_NAME)
    set(META_CONFIG_SUFFIX "-${META_CONFIG_NAME}")
endif ()
if (CONFIGURATION_DISPLAY_NAME)
    set(META_APP_NAME "${META_APP_NAME} (${CONFIGURATION_DISPLAY_NAME})")
endif ()

set(NAMESPACE
    ""
    CACHE STRING "adds a prefix to files and folders installed in the OS toplevel")
if (NAMESPACE)
    set(NAMESPACE_PREFIX "${NAMESPACE}-")
    set(TARGET_PREFIX "${NAMESPACE}-")
endif ()

# allow setting a library/application target suffix - A different configuration name might not require a different target
# name since it might differ anyways (e.g. library extensions for static and shared configuration). Hence there's not simply
# the configuration name used to distinguish targets as well.
if (NOT DEFINED ${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_TARGET_SUFFIX)
    # wrap this within "if (NOT DEFINED" so absence of a target suffix can be enforced within certain project files
    set(${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_TARGET_SUFFIX
        ""
        CACHE STRING "sets a target suffix for ${META_PROJECT_NAME}")
endif ()
set(CONFIGURATION_TARGET_SUFFIX
    ""
    CACHE STRING "sets the target suffix for all projects within the current build")
if (${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_TARGET_SUFFIX STREQUAL "none")
    set(TARGET_SUFFIX "")
elseif (${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_TARGET_SUFFIX)
    set(TARGET_SUFFIX "-${${META_PROJECT_VARNAME_UPPER}_CONFIGURATION_TARGET_SUFFIX}")
elseif (CONFIGURATION_TARGET_SUFFIX)
    set(TARGET_SUFFIX "-${CONFIGURATION_TARGET_SUFFIX}")
endif ()

# disable linking against default Qt plugins by default
if (NOT DEFINED META_QT_DEFAULT_PLUGINS)
    # note: The CMake modules in qtutilities take care of linking against static plugins on their own because old Qt versions
    # did not provide any support and one had to do it manually. Considering that with Qt's default my projects end up
    # pulling in needlessly many plugins I prefer to keep my current approach and disable Qt's defaults.
    set(META_QT_DEFAULT_PLUGINS 0) # needs to be exactly 0, Qt's code uses STREQUAL 0
endif ()

include(DevelUtilities)

# find standard installation directories - note: Allow overriding CMAKE_INSTALL_LIBDIR and LIB_INSTALL_DIR but don't use the
# default from GNUInstallDirs (as an Arch Linux user this feels odd and I also want to avoid breaking existing build
# scripts).
set(LIB_INSTALL_DIR
    "lib"
    CACHE STRING "sets the directory to install libraries to (within the prefix)")
set(CMAKE_INSTALL_LIBDIR
    "${LIB_INSTALL_DIR}"
    CACHE STRING "sets the directory to install libraries to (within the prefix)")
include(GNUInstallDirs)

# define a few variables
set(META_TARGET_NAME "${TARGET_PREFIX}${META_PROJECT_NAME}${TARGET_SUFFIX}")
set(META_DATA_DIR "${CMAKE_INSTALL_DATAROOTDIR}/${NAMESPACE_PREFIX}${META_PROJECT_NAME}${META_CONFIG_SUFFIX}")
set(META_DATA_DIR_ABSOLUTE "${CMAKE_INSTALL_FULL_DATAROOTDIR}/${NAMESPACE_PREFIX}${META_PROJECT_NAME}${META_CONFIG_SUFFIX}")
string(TOUPPER "${CMAKE_BUILD_TYPE}" META_CURRENT_CONFIGURATION)

# set META_GENERIC_NAME to META_APP_NAME if not specified explicitly
if (NOT META_GENERIC_NAME)
    set(META_GENERIC_NAME "${META_APP_NAME}")
endif ()

# set default CXX_STANDARD for all library, application and test targets
if (NOT META_CXX_STANDARD)
    if (MSVC)
        set(META_CXX_STANDARD 20) # MSVC needs C++ 20 mode for designated initializers
    else ()
        set(META_CXX_STANDARD 17)
    endif ()
endif ()

# set version to 0.0.0 if not specified explicitly
if (NOT META_VERSION_MAJOR)
    set(META_VERSION_MAJOR 0)
endif ()
if (NOT META_VERSION_MINOR)
    set(META_VERSION_MINOR 0)
endif ()
if (NOT META_VERSION_PATCH)
    set(META_VERSION_PATCH 0)
endif ()

# set META_ID to target name if not specified
if (NOT META_ID)
    set(META_ID "${META_TARGET_NAME}")
endif ()

# set bugtracker URL
if (NOT META_APP_BUGTRACKER_URL)
    if (META_APP_URL MATCHES "https://(github.com|gitlab.com|.*/(gogs|gitea)|(gogs|gitea).*)/.*")
        set(META_APP_BUGTRACKER_URL "${META_APP_URL}/issues")
    else ()
        set(META_APP_BUGTRACKER_URL "${META_APP_URL}")
    endif ()
endif ()

# determine license automatically from LICENSE file
if (NOT META_PROJECT_LICENSE)
    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" META_PROJECT_LICENSE_FILE)
    elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE" META_PROJECT_LICENSE_FILE)
    elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../LICENSE")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../../LICENSE" META_PROJECT_LICENSE_FILE)
    endif ()
    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/README.md" META_PROJECT_README_FILE)
    elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../README.md")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../README.md" META_PROJECT_README_FILE)
    elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md")
        file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md" META_PROJECT_README_FILE)
    endif ()
    if (META_PROJECT_LICENSE_FILE MATCHES "GNU GENERAL PUBLIC LICENSE.*Version ([1-9\\.]*)")
        set(META_PROJECT_LICENSE "GPL-${CMAKE_MATCH_1}")
        set(OR_LATER_REGEX "\\[${META_PROJECT_LICENSE}-or-later\\]\\(LICENSE\\)")
        if (NOT META_PROJECT_LICENSE MATCHES "\\.")
            set(META_PROJECT_LICENSE "${META_PROJECT_LICENSE}.0")
        endif ()
        if (META_PROJECT_README_FILE MATCHES "${OR_LATER_REGEX}")
            set(META_PROJECT_LICENSE "${META_PROJECT_LICENSE}+")
        endif ()
    elseif (META_PROJECT_LICENSE_FILE MATCHES "GNU LESSER GENERAL PUBLIC LICENSE.*Version ([1-9\\.]*)")
        set(META_PROJECT_LICENSE "LGPL-${CMAKE_MATCH_1}")
    elseif (META_PROJECT_LICENSE_FILE MATCHES "MIT License")
        set(META_PROJECT_LICENSE "MIT")
    elseif (META_PROJECT_LICENSE_FILE MATCHES "Mozilla Public License Version ([1-9\\.]*)")
        set(META_PROJECT_LICENSE "MPL-${CMAKE_MATCH_1}")
    else ()
        message(
            WARNING
                "Unable to detect license of ${META_PROJECT_NAME}. Set META_PROJECT_LICENSE manually to silence this warning."
        )
    endif ()
endif ()

# determine RDNS automatically from other meta-data and allow override
set(${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE
    ""
    CACHE STRING "overrides the RDNS used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE)
    set(META_PROJECT_RDNS ${${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE})
endif ()
set(${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE
    ""
    CACHE STRING "overrides the developer ID used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
    set(META_DEVELOPER_ID ${${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE})
endif ()
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE OR ${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
    message(
        WARNING
            "Overriding the RDNS or developer ID is NOT recommended. This feature is only intended to ease "
            "transitioning when a change is required and to create alternative packaging for development and private use.")
endif ()
if (NOT META_PROJECT_RDNS OR NOT META_DEVELOPER_ID)
    string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
    if (NOT META_PROJECT_RDNS_BASE)
        if (META_APP_URL MATCHES ".*github\\.(com|io).*")
            set(META_PROJECT_RDNS_BASE "io.github") # assume GitHub pages
        else ()
            set(META_PROJECT_RDNS_BASE "org")
        endif ()
    endif ()
    set(META_PROJECT_RDNS "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}.${META_PROJECT_NAME}${TARGET_SUFFIX}")
endif ()
if (NOT META_DEVELOPER_ID)
    set(META_DEVELOPER_ID "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}")
endif ()

# provide variables for other projects built as part of the same subdirs project to access files from this project
get_directory_property(HAS_PARENT PARENT_DIRECTORY)
if (HAS_PARENT)
    set(${META_PROJECT_VARNAME_UPPER}_SOURCE_DIR
        "${CMAKE_CURRENT_SOURCE_DIR}"
        PARENT_SCOPE)
    set(${META_PROJECT_VARNAME_UPPER}_BINARY_DIR
        "${CMAKE_CURRENT_BINARY_DIR}"
        PARENT_SCOPE)
    set(${META_PROJECT_NAME}_DIR
        "${CMAKE_CURRENT_BINARY_DIR}"
        PARENT_SCOPE)
    set(RUNTIME_LIBRARY_PATH
        "${CMAKE_CURRENT_BINARY_DIR}" ${RUNTIME_LIBRARY_PATH}
        PARENT_SCOPE)
endif ()

# determine version
set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH})
option(
    APPEND_GIT_REVISION
    "whether the build script should attempt to append the Git revision and latest commit to the version displayed via --help"
    ON)
if (APPEND_GIT_REVISION AND (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" OR EXISTS "${CMAKE_SOURCE_DIR}/.git"))
    find_program(GIT_BIN git)
    execute_process(
        COMMAND ${GIT_BIN} rev-list --count HEAD
        WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
        OUTPUT_VARIABLE META_GIT_REV_COUNT)
    execute_process(
        COMMAND ${GIT_BIN} rev-parse --short HEAD
        WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
        OUTPUT_VARIABLE META_GIT_LAST_COMMIT_ID)
    string(REPLACE "\n" "" META_GIT_REV_COUNT "${META_GIT_REV_COUNT}")
    string(REPLACE "\n" "" META_GIT_LAST_COMMIT_ID "${META_GIT_LAST_COMMIT_ID}")
    if (META_GIT_REV_COUNT AND META_GIT_LAST_COMMIT_ID)
        set(META_APP_VERSION ${META_APP_VERSION}-${META_GIT_REV_COUNT}.${META_GIT_LAST_COMMIT_ID})
    endif ()
endif ()

# set TARGET_EXECUTABLE which is used to refer to the target executable at its installation location
set(TARGET_EXECUTABLE "${CMAKE_INSTALL_FULL_BINDIR}/${META_TARGET_NAME}")

# create header for feature detection (TODO: remove this in v6 as WriteCompilerDetectionHeader has been deprecated)
if (META_FEATURES_FOR_COMPILER_DETECTION_HEADER)
    include(WriteCompilerDetectionHeader)
    write_compiler_detection_header(
        FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/features.h" PREFIX "${META_PROJECT_VARNAME_UPPER}"
        COMPILERS GNU Clang AppleClang
        FEATURES ${META_FEATURES_FOR_COMPILER_DETECTION_HEADER})
endif ()

# disable new ABI
option(FORCE_OLD_ABI "specifies whether usage of libstdc++'s old ABI should be forced" OFF)
if (FORCE_OLD_ABI)
    list(APPEND META_PRIVATE_COMPILE_DEFINITIONS _GLIBCXX_USE_CXX11_ABI=0)
    message(STATUS "Forcing usage of old CXX11-ABI of libstdc++ (has no effect when a different standard library is used).")
else ()
    message(STATUS "Using default CXX11-ABI (not forcing old CXX11-ABI of libstdc++).")
endif ()

# enable debug-only code when doing a debug build
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    list(APPEND META_PRIVATE_COMPILE_DEFINITIONS CPP_UTILITIES_DEBUG_BUILD)
    message(STATUS "Debug build enabled.")
endif ()

# enable logging when option is set
option(LOGGING_ENABLED "specifies whether logging is enabled" OFF)
if (LOGGING_ENABLED)
    list(APPEND META_PRIVATE_COMPILE_DEFINITIONS LOGGING_ENABLED)
    message(STATUS "Logging is enabled.")
endif ()

# determine whether the project is a header-only library
if (SRC_FILES
    OR GUI_SRC_FILES
    OR WIDGETS_SRC_FILES
    OR WIDGETS_UI_FILES
    OR QML_SRC_FILES
    OR RES_FILES)
    set(META_HEADER_ONLY_LIB NO)
else ()
    set(META_HEADER_ONLY_LIB YES)
    if ("${META_PROJECT_TYPE}" STREQUAL "application")
        message(FATAL_ERROR "Project ${META_PROJECT_NAME} is supposed to be an application but has only header files.")
    endif ()
    message(STATUS "Project ${META_PROJECT_NAME} is header-only library.")
endif ()

# ensure 3rdParty has been included to configure BUILD_SHARED_LIBS and STATIC_LINKAGE/STATIC_LIBRARY_LINKAGE
include(3rdParty)

# options for enabling/disabling Qt GUI (if available)
if (WIDGETS_HEADER_FILES
    OR WIDGETS_SRC_FILES
    OR WIDGETS_UI_FILES
    OR META_HAS_WIDGETS_GUI)
    if (META_GUI_OPTIONAL OR META_WIDGETS_GUI_OPTIONAL)
        option(WIDGETS_GUI "enables/disables building the Qt Widgets GUI: yes (default) or no" ON)
    else ()
        set(WIDGETS_GUI ON)
    endif ()
else ()
    set(WIDGETS_GUI OFF)
endif ()
if (QML_HEADER_FILES
    OR QML_SRC_FILES
    OR META_HAS_QUICK_GUI)
    if (META_GUI_OPTIONAL OR META_QUICK_GUI_OPTIONAL)
        option(QUICK_GUI "enables/disables building the Qt Quick GUI: yes (default) or no" ON)
    else ()
        set(QUICK_GUI ON)
    endif ()
else ()
    set(QUICK_GUI OFF)
endif ()

# find coding style (use style from c++utilities if none included in own project dir)
if (NOT META_NO_TIDY)
    set(CLANG_FORMAT_RULES "${CMAKE_CURRENT_SOURCE_DIR}/coding-style.clang-format")
    if (CPP_UTILITIES_SOURCE_DIR AND NOT EXISTS "${CLANG_FORMAT_RULES}")
        set(CLANG_FORMAT_RULES "${CPP_UTILITIES_SOURCE_DIR}/coding-style.clang-format")
    endif ()
    if (NOT EXISTS "${CLANG_FORMAT_RULES}")
        set(CLANG_FORMAT_RULES "${CPP_UTILITIES_DATA_DIRS}/codingstyle.clang-format")
    endif ()
endif ()

# enable testing
enable_testing()
get_directory_property(HAS_PARENT PARENT_DIRECTORY)
if (HAS_PARENT)
    message(STATUS "For the check target to work, it is required to call enable_testing() on the source directory root.")
endif ()

# make finding testfiles in out-of-source-tree build more convenient by adding a reference to the source directory (not only
# useful if there's a test target; this is for instance also used in mocked configuration of syncthingtray) -> add a file
# called "srcdirref" to the build directory; this file contains the path of the sources so tests can easily find test files
# contained in the source directory
if (NOT META_SRCDIR_REFS)
    set(META_SRCDIR_REFS "${CMAKE_CURRENT_SOURCE_DIR}")
endif ()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/srcdirref" "${META_SRCDIR_REFS}")
# -> ensure the directory "testfiles" exists in the build directory; tests of my projects use it by default to create working
# copies of testfiles
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/testfiles")

# determine source files which might be passed to clang-format or clang-tidy
set(FORMATABLE_FILES
    ${HEADER_FILES}
    ${SRC_FILES}
    ${TEST_HEADER_FILES}
    ${TEST_SRC_FILES}
    ${GUI_HEADER_FILES}
    ${GUI_SRC_FILES}
    ${WIDGETS_HEADER_FILES}
    ${WIDGETS_SRC_FILES}
    ${QML_HEADER_FILES}
    ${QML_SRC_FILES}
    ${EXCLUDED_FILES})
if (QT_TESTS)
    foreach (QT_TEST ${QT_TESTS})
        list(APPEND FORMATABLE_FILES "tests/${QT_TEST}.cpp")
    endforeach ()
endif ()
# only format C/C++ files (and not eg. QML files)
if (FORMATABLE_FILES)
    list(FILTER FORMATABLE_FILES INCLUDE REGEX ".*\\.(c|cc|cpp|h|hh|hpp)")
    if (FORMATABLE_FILES AND META_TIDY_EXCLUDE_REGEX)
        list(FILTER FORMATABLE_FILES EXCLUDE REGEX "${META_TIDY_EXCLUDE_REGEX}")
    endif ()
endif ()

# determine source files which might be passed to cmake-format
set(FORMATABLE_FILES_CMAKE ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt ${CMAKE_MODULE_FILES})

# add command for symlinking clang-{format,tidy} rules so the tools can find it
if (EXISTS "${CLANG_FORMAT_RULES}")
    add_custom_command(
        OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format"
        COMMAND "${CMAKE_COMMAND}" -E create_symlink "${CLANG_FORMAT_RULES}" "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format"
        COMMENT "Linking coding style from ${CLANG_FORMAT_RULES}")
else ()
    message(WARNING "Format rules for clang-format not found.")
endif ()

# allow user to configure creation of tidy targets unless the project disables this via META_NO_TIDY
if (NOT META_NO_TIDY)
    find_program(CLANG_FORMAT_BIN clang-format)
    find_program(CMAKE_FORMAT_BIN cmake-format)
    set(CLANG_FORMAT_ENABLED_DEFAULT OFF)
    set(CMAKE_FORMAT_ENABLED_DEFAULT OFF)
    if (CLANG_FORMAT_BIN)
        set(CLANG_FORMAT_ENABLED_DEFAULT ON)
    endif ()
    if (CMAKE_FORMAT_BIN)
        set(CMAKE_FORMAT_ENABLED_DEFAULT ON)
    endif ()
    set(TIDY_TESTS_ENABLED_DEFAULT OFF)
    if (ENABLE_DEVEL_DEFAULTS AND CLANG_FORMAT_ENABLED_DEFAULT)
        set(TIDY_TESTS_ENABLED_DEFAULT ON)
    endif ()
    option(CLANG_FORMAT_ENABLED "enables creation of tidy target using clang-format" "${CLANG_FORMAT_ENABLED_DEFAULT}")
    option(CMAKE_FORMAT_ENABLED "enables creation of tidy target using cmake-format" "${CMAKE_FORMAT_ENABLED_DEFAULT}")
    option(TIDY_TESTS_ENABLED "enables tests for checking whether code is well-formatted using clang-format"
           "${TIDY_TESTS_ENABLED_DEFAULT}")
endif ()

# add target for tidying with clang-format
if (NOT META_NO_TIDY
    AND CLANG_FORMAT_ENABLED
    AND FORMATABLE_FILES
    AND EXISTS "${CLANG_FORMAT_RULES}")
    if (NOT CLANG_FORMAT_BIN)
        message(FATAL_ERROR "Unable to add tidy target; clang-format not found")
    endif ()
    add_custom_target(
        "${META_TARGET_NAME}_tidy"
        COMMAND "${CLANG_FORMAT_BIN}" -style=file -i ${FORMATABLE_FILES}
        WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
        COMMENT "Tidying ${META_PROJECT_NAME} sources using clang-format"
        DEPENDS "${FORMATABLE_FILES};${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
    if (NOT TARGET tidy)
        add_custom_target(tidy)
    endif ()
    add_dependencies(tidy "${META_TARGET_NAME}_tidy")

    # also add a test to verify whether sources are tidy
    if (TIDY_TESTS_ENABLED)
        add_test(
            NAME "${META_TARGET_NAME}_tidy_test"
            COMMAND "${CLANG_FORMAT_BIN}" -output-replacements-xml -style=file ${FORMATABLE_FILES}
            WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
        list(APPEND CHECK_TARGET_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
        set_tests_properties(
            "${META_TARGET_NAME}_tidy_test" PROPERTIES FAIL_REGULAR_EXPRESSION "<replacement.*>.*</replacement>"
                                                       REQUIRED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
    endif ()
endif ()

# add target for tidying with cmake-format
if (NOT META_NO_TIDY
    AND CMAKE_FORMAT_ENABLED
    AND FORMATABLE_FILES_CMAKE)
    if (NOT CMAKE_FORMAT_BIN)
        message(FATAL_ERROR "Unable to add tidy target; cmake-format not found")
    endif ()
    if (NOT META_CMAKE_FORMAT_OPTIONS)
        set(META_CMAKE_FORMAT_OPTIONS --tab-size=4 --separate-ctrl-name-with-space=True --line-width=125 --autosort=False)
    endif ()
    set(CMAKE_FORMAT_COMMANDS)
    foreach (FILE_TO_FORMAT ${FORMATABLE_FILES_CMAKE})
        list(
            APPEND
            CMAKE_FORMAT_COMMANDS
            COMMAND
            "${CMAKE_FORMAT_BIN}"
            --in-place
            ${META_CMAKE_FORMAT_OPTIONS}
            "${FILE_TO_FORMAT}")
    endforeach ()
    add_custom_target(
        "${META_TARGET_NAME}_cmake_tidy"
        ${CMAKE_FORMAT_COMMANDS}
        WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
        COMMENT "Tidying ${META_PROJECT_NAME} sources using cmake-format"
        DEPENDS "${FORMATABLE_FILES_CMAKE}")
    if (NOT TARGET tidy)
        add_custom_target(tidy)
    endif ()
    add_dependencies(tidy "${META_TARGET_NAME}_cmake_tidy")
endif ()

# add target for static code analysis using clang-tidy
if (NOT META_NO_STATIC_ANALYSIS AND FORMATABLE_FILES)
    option(CLANG_TIDY_ENABLED "enables creation of static-check target using clang-tidy" "${ENABLE_DEVEL_DEFAULTS}")
    set(CLANG_TIDY_CHECKS
        ""
        CACHE STRING
              "-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,performance-*,portability-*,readability-*,android-*")
    if (CLANG_TIDY_ENABLED)
        find_program(CLANG_TIDY_BIN clang-tidy)
        if (NOT CLANG_TIDY_BIN)
            message(FATAL_ERROR "Unable to add tidy target; clang-tidy not found")
        endif ()

        set(CLANG_TIDY_DEPENDS ${FORMATABLE_FILES})

        # compose options for clang-tidy
        set(CLANG_TIDY_OPTIONS -checks="${CLANG_TIDY_CHECKS}" -header-filter="^${META_PROJECT_NAME}/")
        if (EXISTS "${CLANG_FORMAT_RULES}")
            list(APPEND CLANG_TIDY_OPTIONS "-format-style=file")
            list(APPEND CLANG_TIDY_DPENDS "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
        endif ()

        # compose CXX flags for clang-tidy
        set(CLANG_TIDY_CXX_FLAGS "")
        if (NOT META_HEADER_ONLY_LIB)
            # deduce flags from target, set c++ standard
            list(APPEND CLANG_TIDY_CXX_FLAGS "-std=c++$<TARGET_PROPERTY:${META_TARGET_NAME},CXX_STANDARD>")
            # add compile flags
            set(PROP "$<TARGET_PROPERTY:${META_TARGET_NAME},COMPILE_FLAGS>")
            list(APPEND CLANG_TIDY_CXX_FLAGS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
            # add compile definitions
            set(PROP "$<TARGET_PROPERTY:${META_TARGET_NAME},COMPILE_DEFINITIONS>")
            list(APPEND CLANG_TIDY_CXX_FLAGS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
            # add include directories
            set(PROP "$<TARGET_PROPERTY:${META_TARGET_NAME},INCLUDE_DIRECTORIES>")
            list(APPEND CLANG_TIDY_CXX_FLAGS "$<$<BOOL:${PROP}>:-I$<JOIN:${PROP},$<SEMICOLON>-I>>")
        else ()
            # set at least c++ standard for header-only libs
            list(APPEND CLANG_TIDY_CXX_FLAGS "-std=c++${META_CXX_STANDARD}")
        endif ()

        # add a custom command for each source file
        set(CLANG_TIDY_SYMBOLIC_OUTPUT_FILES "")
        foreach (FILE ${FORMATABLE_FILES})
            # skip header files
            if (${FILE} MATCHES ".*\.h")
                continue()
            endif ()

            # use symbolic output file since there's no actual output file (we're just interested in the log)
            set(SYMBOLIC_OUTPUT_FILE "${FILE}.clang-tidy-output")
            list(APPEND CLANG_TIDY_SYMBOLIC_OUTPUT_FILES "${SYMBOLIC_OUTPUT_FILE}")

            add_custom_command(
                OUTPUT "${SYMBOLIC_OUTPUT_FILE}"
                COMMAND "${CLANG_TIDY_BIN}" ${FILE} -- ${CLANG_TIDY_CXX_FLAGS}
                WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
                COMMENT "Linting ${FILE} using clang-tidy"
                DEPENDS "${FILE}"
                COMMAND_EXPAND_LISTS VERBATIM)
        endforeach ()

        # mark all symbolic output files actually as symbolic
        set_source_files_properties(${CLANG_TIDY_SYMBOLIC_OUTPUT_FILES} PROPERTIES SYMBOLIC YES)

        # add targets
        add_custom_target(
            "${META_TARGET_NAME}_static_check"
            DEPENDS ${CLANG_TIDY_SYMBOLIC_OUTPUT_FILES}
            COMMENT "Linting ${META_TARGET_NAME} sources using clang-tidy")
        if (NOT TARGET static-check)
            add_custom_target(static-check)
        endif ()
        add_dependencies(static-check "${META_TARGET_NAME}_static_check")
    endif ()
endif ()

# add autotools-style check target
if (NOT TARGET check)
    set(CMAKE_CTEST_COMMAND ${CMAKE_CTEST_COMMAND} -V)
    add_custom_target(
        check
        COMMAND ${CMAKE_CTEST_COMMAND}
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        DEPENDS "${CHECK_TARGET_DEPENDS}"
        USES_TERMINAL)
endif ()

# enable source code based coverage analysis using clang
option(CLANG_SOURCE_BASED_COVERAGE_ENABLED "enables creation of coverage targets for source-based coverage with clang" OFF)
if (CLANG_SOURCE_BASED_COVERAGE_ENABLED)
    if (NOT CMAKE_HOST_UNIX OR NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
        message(FATAL_ERROR "Source-based coverage only available under UNIX with Clang")
    endif ()
    set(CLANG_SOURCE_BASED_COVERAGE_AVAILABLE YES)
    set(CLANG_SOURCE_BASED_COVERAGE_FLAGS -fprofile-instr-generate -fcoverage-mapping)
    list(APPEND META_PRIVATE_COMPILE_OPTIONS ${CLANG_SOURCE_BASED_COVERAGE_FLAGS})
    list(APPEND META_ADDITIONAL_LINK_FLAGS ${CLANG_SOURCE_BASED_COVERAGE_FLAGS})
endif ()

# enable coverage analysis with GCC and gcov
option(GCOV_COVERAGE_ENABLED "enables creation of coverage targets utilizing gcov" OFF)
if (GCOV_COVERAGE_ENABLED)
    if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
        message(FATAL_ERROR "gcov coverage only available under GCC, current compiler is ${CMAKE_CXX_COMPILER_ID}")
    endif ()
    set(GCC_COVERAGE_AVAILABLE YES)
    set(GCC_COVERAGE_FLAGS --coverage -fprofile-arcs -ftest-coverage)
    list(APPEND META_PRIVATE_COMPILE_OPTIONS ${GCC_COVERAGE_FLAGS})
    list(APPEND META_ADDITIONAL_LINK_FLAGS ${GCC_COVERAGE_FLAGS})
endif ()

# configure creation of install targets
if (NOT META_NO_INSTALL_TARGETS)
    # install targets have not been disabled on project level check whether install targets are disabled by the user this
    # might be useful since install targets seem to cause problems under MacOS
    option(ENABLE_INSTALL_TARGETS "enables creation of install targets" ON)
endif ()

# add install target for extra files
if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
    foreach (EXTRA_FILE ${EXTRA_FILES})
        get_filename_component(EXTRA_DIR ${EXTRA_FILE} DIRECTORY)
        install(
            FILES ${EXTRA_FILE}
            DESTINATION "${META_DATA_DIR}/${EXTRA_DIR}"
            COMPONENT extra-files)
    endforeach ()
    if (NOT TARGET install-extra-files)
        add_custom_target(install-extra-files COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=extra-files -P
                                                      "${CMAKE_BINARY_DIR}/cmake_install.cmake")
    endif ()
endif ()

# determine library directory suffix - Applications might be built as libraries under some platforms (eg. Android). Hence
# this is part of BasicConfig and not LibraryTarget.
set(LIB_SUFFIX
    ""
    CACHE STRING "specifies the general suffix for the library directory")
set(SELECTED_LIB_SUFFIX "${LIB_SUFFIX}")
set(LIB_SUFFIX_32
    ""
    CACHE STRING "specifies the suffix for the library directory to be used when building 32-bit library")
set(LIB_SUFFIX_64
    ""
    CACHE STRING "specifies the suffix for the library directory to be used when building 64-bit library")
if (LIB_SUFFIX_64 AND CMAKE_SIZEOF_VOID_P MATCHES "8")
    set(SELECTED_LIB_SUFFIX "${LIB_SUFFIX_64}")
elseif (LIB_SUFFIX_32 AND CMAKE_SIZEOF_VOID_P MATCHES "4")
    set(SELECTED_LIB_SUFFIX "${LIB_SUFFIX_32}")
endif ()

# ignore LIB_SUFFIX variables if CMAKE_INSTALL_LIBDIR ends with that suffix anyways (%cmake RPM macro apparently passes
# LIB_SUFFIX and CMAKE_INSTALL_LIBDIR/LIB_INSTALL_DIR at the same time)
if (CMAKE_INSTALL_LIBDIR MATCHES ".*${SELECTED_LIB_SUFFIX}$")
    set(SELECTED_LIB_SUFFIX "")
endif ()

set(BIN_INSTALL_DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}")
set(LIB_INSTALL_DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}${SELECTED_LIB_SUFFIX}")

# allow user to specify additional libraries to link against (see buildvariables.md for details)
set(USER_DEFINED_ADDITIONAL_LIBRARIES
    ""
    CACHE STRING "specifies additional libraries to link against (added after any other libraries to the linker line)")
function (append_user_defined_additional_libraries)
    if (NOT USER_DEFINED_ADDITIONAL_LIBRARIES)
        return()
    endif ()

    # find the last library
    set(LIBS PRIVATE_LIBRARIES)
    list(LENGTH ${LIBS} LIB_COUNT)
    if (LIB_COUNT LESS_EQUAL 0)
        set(LIBS PUBLIC_LIBRARIES)
        list(LENGTH ${LIBS} LIB_COUNT)
    endif ()
    if (LIB_COUNT LESS_EQUAL 0)
        # just add the additional libs to PRIVATE_LIBRARIES if there are no libs yet anyways
        set(PRIVATE_LIBRARIES
            "${USER_DEFINED_ADDITIONAL_LIBRARIES}"
            PARENT_SCOPE)
    endif ()
    math(EXPR LAST_LIB_INDEX "${LIB_COUNT} - 1")
    list(GET ${LIBS} ${LAST_LIB_INDEX} LAST_LIB)

    # add the additional libs as INTERFACE_LINK_LIBRARIES of the last lib if it is a target
    if (TARGET "${LAST_LIB}")
        # note: Otherwise the INTERFACE_LINK_LIBRARIES of the last target might still come after the
        # USER_DEFINED_ADDITIONAL_LIBRARIES on the linker line.
        set_property(
            TARGET "${LAST_LIB}"
            APPEND
            PROPERTY INTERFACE_LINK_LIBRARIES ${USER_DEFINED_ADDITIONAL_LIBRARIES})

        return()
    endif ()

    # fall back to simply append the library to PRIVATE_LIBRARIES
    set(PRIVATE_LIBRARIES
        "${USER_DEFINED_ADDITIONAL_LIBRARIES}"
        PARENT_SCOPE)
endfunction ()

# locate PNG icon which used for generating icons for the Windows executable and the macOS bundle
if (PNG_ICON_PATH)
    if (NOT EXISTS "${PNG_ICON_PATH}")
        message(FATAL_ERROR "The specified PNG_ICON_PATH \"${PNG_ICON_PATH}\" is invalid.")
    endif ()
else ()
    if (PNG_ICON_SIZE)
        set(PNG_ICON_SIZES_TO_TEST "${PNG_ICON_SIZE}")
    else ()
        set(PNG_ICON_SIZES_TO_TEST 256 128 64 32 16)
    endif ()
    foreach (POSSIBLE_PNG_ICON_SIZE ${PNG_ICON_SIZES_TO_TEST})
        set(PNG_ICON_PATH
            "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons/hicolor/${POSSIBLE_PNG_ICON_SIZE}x${POSSIBLE_PNG_ICON_SIZE}/apps/${META_PROJECT_NAME}.png"
        )
        if (EXISTS "${PNG_ICON_PATH}")
            set(PNG_ICON_SIZE "${POSSIBLE_PNG_ICON_SIZE}")
            message(STATUS "Using PNG icon from \"${PNG_ICON_PATH}\".")
            break()
        endif ()
        unset(PNG_ICON_PATH)
    endforeach ()
endif ()

# configure warnings
configure_development_warnings(APPEND_OUTPUT_VAR META_PRIVATE_COMPILE_OPTIONS)
if (MSVC)
    list(APPEND META_PRIVATE_COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS=1)
endif ()

# add target to symlink compile_commands.json into source directory
if (ENABLE_EXPORT_COMPILE_COMMANDS)
    set(COMPILE_COMMANDS_SOURCE "${CMAKE_BINARY_DIR}/compile_commands.json")
    set(COMPILE_COMMANDS_DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json")
    add_custom_command(
        OUTPUT "${COMPILE_COMMANDS_DESTINATION}"
        COMMAND "${CMAKE_COMMAND}" -E create_symlink "${COMPILE_COMMANDS_SOURCE}" "${COMPILE_COMMANDS_DESTINATION}"
        COMMENT "Linking compile_commands.json ${COMPILE_COMMANDS_DESTINATION}")
    add_custom_target(
        "${META_TARGET_NAME}_link_compile_commands"
        COMMENT "Linking compile_commands.json for ${META_PROJECT_NAME}"
        DEPENDS "${COMPILE_COMMANDS_DESTINATION}")
    if (NOT TARGET link_compile_commands)
        add_custom_target(link_compile_commands)
    endif ()
    add_dependencies(link_compile_commands "${META_TARGET_NAME}_link_compile_commands")
endif ()

set(BASIC_PROJECT_CONFIG_DONE YES)