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)
|