From f8bb04f17b0705f3428183284910d1535375222a Mon Sep 17 00:00:00 2001 From: Vitalii Shylienkov Date: Sun, 1 May 2022 17:08:34 -0400 Subject: [PATCH 1/2] crc: basic CMake support --- .clang-tidy | 79 ++++++++ .gitignore | 3 + CMakeLists.txt | 208 ++++++++++++++++++++++ cmake/CMakeLists.txt | 3 + cmake/finders/FindClangTidy.cmake | 136 ++++++++++++++ cmake/finders/FindGcov.cmake | 148 +++++++++++++++ cmake/finders/FindGcovr.cmake | 145 +++++++++++++++ cmake/finders/FindIncludeWhatYouUse.cmake | 81 +++++++++ cmake/modules/CodeCoverage.cmake | 145 +++++++++++++++ cmake/modules/CommonOptions.cmake | 53 ++++++ cmake/modules/UseClangTidy.cmake | 62 +++++++ cmake/modules/UseGTest.cmake | 86 +++++++++ cmake/modules/UseIncludeWhatYouUse.cmake | 19 ++ cmake/templates/crc.pc.in | 9 + cmake/templates/crcConfig.cmake.in | 9 + examples/CMakeLists.txt | 21 +++ include/CMakeLists.txt | 34 ++++ precalc/CMakeLists.txt | 27 +++ test/CMakeLists.txt | 57 ++++++ 19 files changed, 1325 insertions(+) create mode 100644 .clang-tidy create mode 100644 CMakeLists.txt create mode 100644 cmake/CMakeLists.txt create mode 100644 cmake/finders/FindClangTidy.cmake create mode 100644 cmake/finders/FindGcov.cmake create mode 100644 cmake/finders/FindGcovr.cmake create mode 100644 cmake/finders/FindIncludeWhatYouUse.cmake create mode 100644 cmake/modules/CodeCoverage.cmake create mode 100644 cmake/modules/CommonOptions.cmake create mode 100644 cmake/modules/UseClangTidy.cmake create mode 100644 cmake/modules/UseGTest.cmake create mode 100644 cmake/modules/UseIncludeWhatYouUse.cmake create mode 100644 cmake/templates/crc.pc.in create mode 100644 cmake/templates/crcConfig.cmake.in create mode 100644 examples/CMakeLists.txt create mode 100644 include/CMakeLists.txt create mode 100644 precalc/CMakeLists.txt create mode 100644 test/CMakeLists.txt diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..bbb7ed5 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,79 @@ +--- +Checks: > + , clang-diagnostic-* + , clang-analyzer-* + , bugprone-* + , -bugprone-easily-swappable-parameters + , -bugprone-implicit-widening-of-multiplication-result + , -bugprone-narrowing-conversions + , -bugprone-reserved-identifier + , cert-* + , -cert-dcl37-c + , -cert-dcl51-cpp + , -cert-env33-c + , concurrency-* + , cppcoreguidelines-* + , -cppcoreguidelines-avoid-c-arrays + , -cppcoreguidelines-avoid-magic-numbers + , -cppcoreguidelines-narrowing-conversions + , -cppcoreguidelines-non-private-member-variables-in-classes + , -cppcoreguidelines-owning-memory + , -cppcoreguidelines-pro-bounds-array-to-pointer-decay + , -cppcoreguidelines-pro-bounds-constant-array-index + , -cppcoreguidelines-pro-type-union-access + , -cppcoreguidelines-pro-type-vararg + , google-* + , -google-readability-todo + , misc-* + , -misc-non-private-member-variables-in-classes + , modernize-* + , -modernize-avoid-c-arrays + , -modernize-use-override + , -modernize-use-trailing-return-type + , performance-* + , portability-* + , readability-* + , -readability-avoid-const-params-in-decls + , -readability-implicit-bool-conversion + , -readability-magic-numbers + , -readability-suspicious-call-argument +WarningsAsErrors: '' +HeaderFilterRegex: 'acp_hub_[^/]*\.(h|hpp|hxx)$' +FormatStyle: file +CheckOptions: + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: 'false' + - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons + value: 'false' + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: 'true' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: llvm-else-after-return.WarnOnConditionVariables + value: 'false' + - key: llvm-else-after-return.WarnOnUnfixable + value: 'false' + - key: llvm-qualified-auto.AddConstToQualified + value: 'false' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + +... diff --git a/.gitignore b/.gitignore index 95b5676..327adfb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ tstcrc bin/prc.exe bin/prc *.o +build +compile_commands.json +.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8f7eee7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,208 @@ +# Requirements to the cmake ---------------------------------------------------- +cmake_minimum_required(VERSION 3.12) + +# Project Declaration ---------------------------------------------------------- + +project(crc + VERSION 2.1.0 + DESCRIPTION "Multi platform MIT licensed CRC library in C" + HOMEPAGE_URL "https://github.com/lammertb/libcrc" + LANGUAGES C +) + +# CMake extensions ------------------------------------------------------------- +add_subdirectory(cmake) + +# Includes --------------------------------------------------------------------- +## cmake native modules +include(CheckCCompilerFlag) +include(CMakePackageConfigHelpers) +include(FeatureSummary) +include(GNUInstallDirs) + +## local modules +include(CommonOptions) + +if(WITH_INCLUDE_WHAT_YOU_USE) + include(UseIncludeWhatYouUse) +endif() + +if(WITH_CLANG_TIDY) + include(UseClangTidy) +endif() + +if(WITH_COVERAGE) + include(CodeCoverage) +endif() + +# Package setting ------------------------------------------------------------- +set(PACKAGE_NAME ${PROJECT_NAME}) +set(PACKAGE_NAMESPACE ${PROJECT_NAME}) +set(PACKAGE_INSTALL_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PACKAGE_NAME}) +set(PACKAGE_INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}/${PACKAGE_NAME}) +set(PACKAGE_INSTALL_LIBRARY_DIR ${CMAKE_INSTALL_LIBDIR}) +if(WIN32) + set(PACKAGE_INSTALL_CMAKE_DIR cmake) + set(PACKAGE_INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}) +endif() + + +# Target Declaration ----------------------------------------------------------- +add_library(${PROJECT_NAME} STATIC) +add_library(${PACKAGE_NAMESPACE}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +# Compilation flags ------------------------------------------------------------ + +add_library(crc_compile_flags INTERFACE) +add_library(${PACKAGE_NAMESPACE}::compile_flags ALIAS crc_compile_flags) + +set(compiler_flags -Wall) +if(WIN32) + list(APPEND compiler_flags -Ox -Ot -MT -GT -J -sdl -WX + -volatile:iso -nologo + -wd4464 -wd4668 -wd4710 -wd4711 -wd4201 -wd4820 + ) +else() + list(APPEND compiler_flags -Wextra -Wstrict-prototypes -Wshadow + -Wpointer-arith -Wcast-qual -Wcast-align + -Wwrite-strings -Wredundant-decls -Wnested-externs + -funsigned-char + ) +endif() + +foreach(compiler_flag IN LISTS compiler_flags) + check_c_compiler_flag(${compiler_flag} supports${compiler_flag}) + if(supports${compiler_flag}) + target_compile_options(crc_compile_flags + INTERFACE + $ + ) + endif() +endforeach() + +# Dependency resolving --------------------------------------------------------- + +add_subdirectory(include) +add_subdirectory(precalc) + + +# File generation -------------------------------------------------------------- +foreach(arch 32 64) + add_custom_command(OUTPUT tab/gentab${arch}.inc + COMMAND ${CMAKE_COMMAND} -E make_directory tab + COMMAND prc --crc${arch} tab/gentab${arch}.inc + COMMENT "precalc: generate tab/gentab${arch}.inc" + VERBATIM + ) +endforeach() + + +add_library(crc_gentab INTERFACE) +add_library(${PACKAGE_NAMESPACE}::gentab ALIAS crc_gentab) +target_sources(crc_gentab + INTERFACE + $ + $ +) +target_include_directories(crc_gentab + INTERFACE + $ +) + +# Target Definition ------------------------------------------------------------ + +target_sources(${PROJECT_NAME} + PRIVATE + src/crc8.c + src/crc16.c + src/crc32.c + src/crc64.c + src/crcccitt.c + src/crcdnp.c + src/crckrmit.c + src/crcsick.c + src/nmea-chk.c +) + +target_link_libraries(${PROJECT_NAME} + PUBLIC + crc::common + PRIVATE + $ + $ +) + +# Package configuration -------------------------------------------------------- +set(PACKAGE_EXPORTS common crc) +configure_package_config_file( + cmake/templates/${PACKAGE_NAME}Config.cmake.in + ${PACKAGE_NAME}Config.cmake + INSTALL_DESTINATION ${PACKAGE_INSTALL_CMAKE_DIR} + NO_SET_AND_CHECK_MACRO +) + +write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake + COMPATIBILITY AnyNewerVersion +) + +# pkgconfig configuration ------------------------------------------------------ +configure_file("cmake/templates/${PACKAGE_NAME}.pc.in" + "${PACKAGE_NAME}.pc" + @ONLY +) + +# Static Code analysis --------------------------------------------------------- +if(WITH_CLANG_TIDY) + target_setup_clang_tidy(${PROJECT_NAME}) +endif() + +# Testing ---------------------------------------------------------------------- +if(WITH_UNIT_TEST) + + if(WITH_COVERAGE) + target_setup_coverage(${PROJECT_NAME}) + endif() + + include(CTest) + enable_testing() + add_subdirectory(test) +endif() + +# Examples --------------------------------------------------------------------- +if(WITH_EXAMPLE) + add_subdirectory(examples) +endif() + +# Feature summary -------------------------------------------------------------- +feature_summary(WHAT ALL + DESCRIPTION "-- [${PROJECT_NAME} summary] ---------------------------------" +) + +# Installations ---------------------------------------------------------------- + +## Targets installation +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION ${PACKAGE_INSTALL_LIBRARY_DIR} + LIBRARY DESTINATION ${PACKAGE_INSTALL_LIBRARY_DIR} + PUBLIC_HEADER DESTINATION ${PACKAGE_INSTALL_INCLUDE_DIR} + COMPONENT ${PROJECT_NAME} +) + +## CMake's configurations +install(EXPORT ${PROJECT_NAME}Targets + NAMESPACE ${PACKAGE_NAMESPACE}:: + DESTINATION ${PACKAGE_INSTALL_CMAKE_DIR} +) + + +## Package configurations +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}ConfigVersion.cmake + DESTINATION ${PACKAGE_INSTALL_CMAKE_DIR} +) + +## pkgconfig configurations ---------------------------------------------------- +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}.pc" + DESTINATION ${PACKAGE_INSTALL_LIBRARY_DIR}/pkgconfig +) \ No newline at end of file diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..affbb54 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,3 @@ +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/modules) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/finders) +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) diff --git a/cmake/finders/FindClangTidy.cmake b/cmake/finders/FindClangTidy.cmake new file mode 100644 index 0000000..6277eb9 --- /dev/null +++ b/cmake/finders/FindClangTidy.cmake @@ -0,0 +1,136 @@ +# includes --------------------------------------------------------------------- +include(FeatureSummary) +include(FindPackageHandleStandardArgs) + +# Internal variables ----------------------------------------------------------- +set(cfp_NAME "${CMAKE_FIND_PACKAGE_NAME}") +string(TOUPPER "${cfp_NAME}" CFP_NAME) +set(${cfp_NAME}_log_prefix "${cfp_NAME}:") + +# Declare package properties --------------------------------------------------- +set_package_properties(${cfp_NAME} + PROPERTIES + URL "https://clang.llvm.org/extra/clang-tidy/index.html" + DESCRIPTION "A clang-based C++ “linter” tool" +) + +# Validate find_package() arguments -------------------------------------------- + +if(${cfp_NAME}_FIND_COMPONENTS AND NOT ${cfp_NAME}_FIND_QUIETLY) + message(WARNING "${${cfp_NAME}_log_prefix} components not supported") +endif() + +# Build list of names ---------------------------------------------------------- +set(${cfp_NAME}_names "clang-tidy") + +set(known_file_suffixes 3.9 4.0 5.0 6.0) +set(known_file_suffixes_major 7 8 9 10 13 14 15) + +if(DEFINED ${cfp_NAME}_FIND_VERSION) # if specific version was requested + if(${cfp_NAME}_FIND_VERSION_EXACT) # if exact this version has to be found + if(${cfp_NAME}_FIND_VERSION VERSION_LESS 7) # we are looking for two components + set(suffix ${${cfp_NAME}_FIND_VERSION_MAJOR}) + + if(${cfp_NAME}_FIND_VERSION_COUNT EQUAL 1) # if only one component provided + string(APPEND suffix ".0") # we explicitly append .0 + else() + string(APPEND suffix ".${${cfp_NAME}_FIND_VERSION_MINOR}") # otherwise we take only two components + endif() + + if(suffix IN_LIST known_file_suffixes) # if we know this version + list(APPEND ${cfp_NAME}_names "clang-tidy-${suffix}") # add to the list of searched names + endif() + else() # one component suffix + if( (${cfp_NAME}_FIND_VERSION_COUNT EQUAL 1) # if requested one component + OR ( (${cfp_NAME}_FIND_VERSION_COUNT EQUAL 2) # or two with the second equals 0 + AND (${cfp_NAME}_FIND_VERSION_MINOR EQUAL 0))) + + if(${cfp_NAME}_FIND_VERSION_MAJOR IN_LIST known_file_suffixes_major) + list(APPEND ${cfp_NAME}_names "clang-tidy-${${cfp_NAME}_FIND_VERSION_MAJOR}") + endif() + endif() + endif() + else() # not exact version requested + foreach(suffix IN LISTS known_file_suffixes known_file_suffixes_major) + if(NOT suffix VERSION_LESS ${cfp_NAME}_FIND_VERSION) + list(APPEND ${cfp_NAME}_names "clang-tidy-${suffix}") + endif() + endforeach() + endif() +else() # no specific version requested + foreach(suffix IN LISTS known_file_suffixes known_file_suffixes_major) + list(APPEND ${cfp_NAME}_names "clang-tidy-${suffix}") + endforeach() +endif() + +list(REVERSE ${cfp_NAME}_names) + +unset(suffix) +unset(known_file_suffixes) +unset(known_file_suffixes_major) + +# Find binary ------------------------------------------------------------------ + +find_program(${cfp_NAME}_EXECUTABLE + NAMES ${${cfp_NAME}_names} + HINTS ${${cfp_NAME}_DIR} ${${CFP_NAME}_DIR} + ENV ${cfp_NAME}_DIR + ENV ${CFP_NAME}_DIR + PATH_SUFFIXES bin + DOC "The ${cfp_NAME} executable" +) + +unset(${cfp_NAME}_names) + +# Figure out the version ------------------------------------------------------- + +if(${cfp_NAME}_EXECUTABLE) + set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}") + mark_as_advanced(${cfp_NAME}_EXECUTABLE ${cfp_NAME}_COMMAND) + + execute_process( + COMMAND ${${cfp_NAME}_EXECUTABLE} --version + RESULT_VARIABLE ${cfp_NAME}_version_result + OUTPUT_VARIABLE ${cfp_NAME}_version_output + ERROR_VARIABLE ${cfp_NAME}_version_error + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + + if(${${cfp_NAME}_version_result} EQUAL 0) + if(${cfp_NAME}_version_output MATCHES "([0-9]+\.[0-9]+\.[0-9]+)") + set(${cfp_NAME}_VERSION_STRING "${CMAKE_MATCH_1}") + + string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" + ${cfp_NAME}_VERSION_MAJOR "${${cfp_NAME}_VERSION_STRING}" + ) + string(REGEX REPLACE "[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" + ${cfp_NAME}_VERSION_MINOR "${${cfp_NAME}_VERSION_STRING}" + ) + string(REGEX REPLACE "[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" + ${cfp_NAME}_VERSION_PATCH "${${cfp_NAME}_VERSION_STRING}" + ) + endif() + else() + if(NOT ${cfp_NAME}_FIND_QUIETLY) + message(WARNING "${${cfp_NAME}_log_prefix}: version query failed: ${${cfp_NAME}_version_error}") + endif() + endif() + + unset(${cfp_NAME}_version_result) + unset(${cfp_NAME}_version_output) + unset(${cfp_NAME}_version_error) +endif() + +# handling --------------------------------------------------------------------- +find_package_handle_standard_args(${cfp_NAME} + REQUIRED_VARS ${cfp_NAME}_EXECUTABLE + ${cfp_NAME}_COMMAND + VERSION_VAR ${cfp_NAME}_VERSION_STRING + FAIL_MESSAGE "Installation: https://apt.llvm.org/" +) + +# clean-up --------------------------------------------------------------------- +unset(${cfp_NAME}_log_prefix) +unset(CFP_NAME) +unset(cfp_NAME) \ No newline at end of file diff --git a/cmake/finders/FindGcov.cmake b/cmake/finders/FindGcov.cmake new file mode 100644 index 0000000..1eb2c43 --- /dev/null +++ b/cmake/finders/FindGcov.cmake @@ -0,0 +1,148 @@ +include(FeatureSummary) +set_package_properties(Gcov + PROPERTIES + URL "https://gcc.gnu.org/onlinedocs/gcc/Gcov.html" + DESCRIPTION "Tool to test code coverage" +) + +macro(find_gnu_gcov_executable) + # potential names: + # gcov + # gcov-x // x - major version of GCC + # ${CROSS_COMPILE}gcov-x + # ${CROSS_COMPILE}gcov + string(REGEX MATCH "^[0-9]+" GCC_VERSION_MAJOR + "${CMAKE_${LANG}_COMPILER_VERSION}" + ) + + set(gcov_filenames gcov-${GCC_VERSION_MAJOR} gcov) + + if(DEFINED ENV{CROSS_COMPILE}) + list(PREPEND gcov_filenames $ENV{CROSS_COMPILE}gcov-${GCC_VERSION_MAJOR} $ENV{CROSS_COMPILE}gcov) + endif() + + # compiler path provided by call site + find_program(Gcov_EXECUTABLE + NAMES ${gcov_filenames} + HINTS "${COMPILER_PATH}" + ) + + if(Gcov_EXECUTABLE) + set(Gcov_EXECUTABLE "${Gcov_EXECUTABLE}" CACHE FILEPATH "") + set(Gcov_COMMAND "${Gcov_EXECUTABLE}" CACHE STRING "") + mark_as_advanced(Gcov_EXECUTABLE Gcov_COMMAND) + endif() +endmacro() + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + +foreach(LANG ${ENABLED_LANGUAGES}) + # no language + if(LANG STREQUAL "NONE") + continue() + endif() + + # if Gcov was already found - skip search + if(Gcov_${CMAKE_${LANG}_COMPILER_ID}_EXECUTABLE) + continue() + endif() + + # gcov usually placed near to the compiler + get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH) + + if("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU") + find_gnu_gcov_executable() + elseif("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "^(Apple)?Clang$") + + # potential names: + # llvm-cov + # llvm-cov-x // x - major version of LLVM + # llvm-cov-x.y // x.y - version string of LLVM + string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION_STRING + "${CMAKE_${LANG}_COMPILER_VERSION}" + ) + + string(REGEX REPLACE "^([0-9]+).[0-9]+" "\\1" LLVM_VERSION_MAJOR + "${LLVM_VERSION_STRING}" + ) + + # llvm-cov version < 3.5 not supported + if(LLVM_VERSION_STRING VERSION_GREATER 3.4) + find_program(LLVM_cov_EXECUTABLE + NAMES "llvm-cov-${LLVM_VERSION_STRING}" + "llvm-cov-${LLVM_VERSION_MAJOR}" + "llvm-cov" + HINTS ${COMPILER_PATH} + ) + + if(LLVM_cov_EXECUTABLE) + set(Gcov_EXECUTABLE "${LLVM_cov_EXECUTABLE}" CACHE FILEPATH "") + # llvm-cov gcov - emulates gcov + file(WRITE + ${CMAKE_BINARY_DIR}/CMakeFiles/gcov + "#!/bin/bash\nexec ${LLVM_cov_EXECUTABLE} gcov \"$@\"" + ) + file(COPY ${CMAKE_BINARY_DIR}/CMakeFiles/gcov + DESTINATION ${CMAKE_BINARY_DIR} + FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ + ) + set(Gcov_COMMAND "${CMAKE_BINARY_DIR}/gcov" CACHE STRING "") + mark_as_advanced(LLVM_cov_EXECUTABLE Gcov_EXECUTABLE Gcov_COMMAND) + else() + # if nothing found try to find GNU gcov + find_gnu_gcov_executable() + endif() + endif() + endif() + + if(Gcov_EXECUTABLE) + # do not repeat search for this compiler anymore + set(Gcov_${CMAKE_${LANG}_COMPILER_ID}_EXECUTABLE "${Gcov_EXECUTABLE}" + CACHE FILEPATH "${LANG} gcov binary." + ) + endif() + +endforeach() + +# get and parse version +# exports: +# Gcov_VERSION_STRING +# Gcov_VERSION_MAJOR +# Gcov_VERSION_MINOR +# Gcov_VERSION_PATCH +if(Gcov_EXECUTABLE) + execute_process( + COMMAND ${Gcov_COMMAND} -version + OUTPUT_VARIABLE Gcov_VERSION_RAW + ERROR_VARIABLE Gcov_VERSION_RAW + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(Gcov_VERSION_RAW MATCHES "([0-9]+\.[0-9]+\.[0-9]+)") + set(Gcov_VERSION_STRING "${CMAKE_MATCH_1}") + string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" + Gcov_VERSION_MAJOR ${Gcov_VERSION_STRING} + ) + string(REGEX REPLACE "[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" + Gcov_VERSION_MINOR ${Gcov_VERSION_STRING} + ) + string(REGEX REPLACE "[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" + Gcov_VERSION_PATCH ${Gcov_VERSION_STRING} + ) + endif() +endif() + +# include required Modules +include(FindPackageHandleStandardArgs) +# FPHSA to cover flags and version +find_package_handle_standard_args(Gcov + REQUIRED_VARS Gcov_EXECUTABLE + Gcov_COMMAND + VERSION_VAR Gcov_VERSION_STRING +) + +# clean-up +unset(ENABLED_LANGUAGES) +unset(Gcov_VERSION_RAW) diff --git a/cmake/finders/FindGcovr.cmake b/cmake/finders/FindGcovr.cmake new file mode 100644 index 0000000..ef2edfa --- /dev/null +++ b/cmake/finders/FindGcovr.cmake @@ -0,0 +1,145 @@ +include(FeatureSummary) +set_package_properties(Gcovr + PROPERTIES + URL "https://gcovr.com/en/stable/" + DESCRIPTION "An utility for managing the use of the GNU gcov utility and generating summarized code coverage results" +) + +# No components supported +if(Gcovr_FIND_COMPONENTS AND NOT Gcovr_FIND_QUIETLY) + message(STATUS "Find Gcovr: components not supported") +endif() + +# Hints where to look for Gcovr executable +# Environment and user variables +set(Gcovr_HINTS "") +foreach(hint Gcovr_DIR GCOVR_DIR) + if(DEFINED ${hint}) + if((EXISTS "${${hint}}") AND (IS_DIRECTORY "${${hint}}")) + list(APPEND Gcovr_HINTS "${${hint}}") + endif() + endif() + if(DEFINED ENV{${hint}}) + if((EXISTS "$ENV{${hint}}") AND (IS_DIRECTORY "$ENV{${hint}}")) + list(APPEND Gcovr_HINTS "$ENV{${hint}}") + endif() + endif() +endforeach() + +# Gcovr can be installed into Python user script directory: +# Unix: ~/.local/bin +# Windows: %APPDATA%/Python/Scripts +# https://www.python.org/dev/peps/pep-0370 +find_package(Python QUIET COMPONENTS Interpreter) + +if(Python_Interpreter_FOUND) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m site --user-base + RESULT_VARIABLE USER_BASE_DIR_RESULT + OUTPUT_VARIABLE USER_BASE_DIR_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(USER_BASE_DIR_RESULT EQUAL 0) + list(APPEND Gcovr_HINTS "${USER_BASE_DIR_OUTPUT}") + endif() +endif() + +find_program(Gcovr_EXECUTABLE + NAMES gcovr + gcovr.py + HINTS ${Gcovr_HINTS} + PATH_SUFFIXES bin Scripts + DOC "The Gcovr executable" +) + +if(Gcovr_EXECUTABLE) + mark_as_advanced(Gcovr_EXECUTABLE) + + file(STRINGS "${Gcovr_EXECUTABLE}" SHEBANG LIMIT_COUNT 1) + + set(Gcovr_PYTHON_COMMAND "${Python_EXECUTABLE}") + set(Gcovr_PYTHON_OPTIONS "") + if(SHEBANG MATCHES "^#!(.*/python.*)$") + # exclude heading or trailing whitespaces + string(REGEX REPLACE "^ +| +$" "" Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}") + + if(Gcovr_PYTHON_COMMAND MATCHES "([^ ]+) (.*)") + # command + set(Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}") + + # options transformed to cmake ;-list + string (REGEX REPLACE " +" ";" Gcovr_PYTHON_OPTIONS "${CMAKE_MATCH_2}") + endif () + elseif(SHEBANG MATCHES "^#!(.*env python.*)$") + # exclude heading or trailing whitespaces + string(REGEX REPLACE "^ +| +$" "" Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}") + + if(Gcovr_PYTHON_COMMAND MATCHES "([^ ]+) (python[23]?) ?(.*)") + # command + set(Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}") + + if(CMAKE_MATCH_3) + # options transformed to cmake ;-list + string (REGEX REPLACE " +" ";" Gcovr_PYTHON_OPTIONS "${CMAKE_MATCH_3}") + endif() + endif () + endif() + + # escape any special charecter + string(REGEX REPLACE "([.+*?^$])" "\\\\\\1" + _Gcovr_PYTHON_COMMAND_RE "${Python_EXECUTABLE}" + ) + + list(FIND Gcovr_PYTHON_OPTIONS -E INDEX) + if((INDEX EQUAL -1) AND + (NOT Gcovr_PYTHON_COMMAND MATCHES "^${_Gcovr_PYTHON_COMMAND_RE}$")) + list(INSERT Gcovr_PYTHON_OPTIONS 0 -E) + endif() +endif() + + +if(Gcovr_EXECUTABLE) + if(Gcovr_PYTHON_COMMAND) + execute_process( + COMMAND ${Gcovr_PYTHON_COMMAND} ${Gcovr_PYTHON_OPTIONS} "${Gcovr_EXECUTABLE}" --version + OUTPUT_VARIABLE Gcovr_VERSION_RAW + ERROR_VARIABLE Gcovr_VERSION_RAW + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE + ) + elseif(UNIX) + execute_process( + COMMAND "${Gcovr_EXECUTABLE}" --version + OUTPUT_VARIABLE Gcovr_VERSION_RAW + ERROR_VARIABLE Gcovr_VERSION_RAW + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE + ) + endif() + + if(Gcovr_VERSION_RAW MATCHES "gcovr ([.0-9]+)") + set(Gcovr_VERSION_STRING "${CMAKE_MATCH_1}") + + string(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" + Gcovr_VERSION_MAJOR ${Gcovr_VERSION_STRING} + ) + string(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" + Gcovr_VERSION_MINOR ${Gcovr_VERSION_STRING} + ) + endif() +endif() + +if(Gcovr_EXECUTABLE) + get_filename_component(Gcovr_DIR "${Gcovr_EXECUTABLE}" PATH) + string(REGEX REPLACE "/bin/?$" "" Gcovr_DIR "${Gcovr_DIR}") + # cache it + set(Gcovr_DIR "${Gcovr_DIR}" CACHE PATH "Gcovr installation path") +endif() + +# handle components, version, quiet, required and other flags +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Gcovr + REQUIRED_VARS Gcovr_EXECUTABLE Gcovr_DIR + VERSION_VAR Gcovr_VERSION_STRING + FAIL_MESSAGE "Install: `python3 -m pip install -U gcovr`.\nRemove default: `sudo apt remove gcovr`\n" +) \ No newline at end of file diff --git a/cmake/finders/FindIncludeWhatYouUse.cmake b/cmake/finders/FindIncludeWhatYouUse.cmake new file mode 100644 index 0000000..075e98b --- /dev/null +++ b/cmake/finders/FindIncludeWhatYouUse.cmake @@ -0,0 +1,81 @@ +# includes --------------------------------------------------------------------- +include(FeatureSummary) +include(FindPackageHandleStandardArgs) + +# Internal variables ----------------------------------------------------------- +set(cfp_NAME "${CMAKE_FIND_PACKAGE_NAME}") +string(TOUPPER "${cfp_NAME}" CFP_NAME) +set(${cfp_NAME}_log_prefix "${cfp_NAME}:") + +# Declare package properties --------------------------------------------------- +set_package_properties(${cfp_NAME} + PROPERTIES + URL "https://include-what-you-use.org/" + DESCRIPTION "A tool for use with clang to analyze #includes in C and C++ source files" +) + +# Validate find_package() arguments -------------------------------------------- + +if(${cfp_NAME}_FIND_COMPONENTS AND NOT ${cfp_NAME}_FIND_QUIETLY) + message(WARNING "${${cfp_NAME}_log_prefix} components not supported") +endif() + +# Find binary ------------------------------------------------------------------ + +find_program(${cfp_NAME}_EXECUTABLE + NAMES include-what-you-use + HINTS ${${cfp_NAME}_DIR} ${${CFP_NAME}_DIR} + ENV ${cfp_NAME}_DIR + ENV ${CFP_NAME}_DIR + PATH_SUFFIXES bin + DOC "The ${cfp_NAME} executable" +) + +# Figure out the version ------------------------------------------------------- + +if(${cfp_NAME}_EXECUTABLE) + set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}") + mark_as_advanced(${cfp_NAME}_EXECUTABLE ${cfp_NAME}_COMMAND) + + execute_process( + COMMAND ${${cfp_NAME}_EXECUTABLE} --version + RESULT_VARIABLE ${cfp_NAME}_version_result + OUTPUT_VARIABLE ${cfp_NAME}_version_output + ERROR_VARIABLE ${cfp_NAME}_version_error + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(${${cfp_NAME}_version_result} EQUAL 0) + if(${cfp_NAME}_version_output MATCHES "include-what-you-use ([0-9]+\.[0-9]+)") + set(${cfp_NAME}_VERSION_STRING "${CMAKE_MATCH_1}") + + string(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" + ${cfp_NAME}_VERSION_MAJOR "${${cfp_NAME}_VERSION_STRING}" + ) + string(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" + ${cfp_NAME}_VERSION_MINOR "${${cfp_NAME}_VERSION_STRING}" + ) + endif() + else() + if(NOT ${cfp_NAME}_FIND_QUIETLY) + message(WARNING "${${cfp_NAME}_log_prefix}: version query failed: ${${cfp_NAME}_version_error}") + endif() + endif() + + unset(${cfp_NAME}_version_result) + unset(${cfp_NAME}_version_output) + unset(${cfp_NAME}_version_error) +endif() + +# handling +find_package_handle_standard_args(${cfp_NAME} + REQUIRED_VARS ${cfp_NAME}_EXECUTABLE + ${cfp_NAME}_COMMAND + VERSION_VAR ${cfp_NAME}_VERSION_STRING + FAIL_MESSAGE "Installation: https://github.com/include-what-you-use/include-what-you-use#how-to-install" +) + +# clean-up --------------------------------------------------------------------- +unset(${cfp_NAME}_log_prefix) +unset(CFP_NAME) +unset(cfp_NAME) \ No newline at end of file diff --git a/cmake/modules/CodeCoverage.cmake b/cmake/modules/CodeCoverage.cmake new file mode 100644 index 0000000..007e31a --- /dev/null +++ b/cmake/modules/CodeCoverage.cmake @@ -0,0 +1,145 @@ +include_guard(GLOBAL) + +# coverage may be set up from another CodeCoverage.cmake file +# include_guard doesn't work here +# use our own guard mechanism +if(TARGET coverage) + return() +endif() + +get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(${module_name}_log_prefix "${module_name}:") + +message(STATUS "${${module_name}_log_prefix} included") + +include(FeatureSummary) + +option(COVERAGE_SORT_LINES "Sort coverage report by lines covered" OFF) +add_feature_info([GLOBAL].CoverageSortedLines + COVERAGE_SORT_LINES + "Sort entries by increasing number of uncovered lines." +) + +option(COVERAGE_SORT_PERCENTAGE + "Sort coverage report by percent of coverage" + OFF +) +add_feature_info([GLOBAL].CoverageSortedPercents + COVERAGE_SORT_PERCENTAGE + "Sort entries by increasing percentage of uncovered lines." +) + +# Environment validation ------------------------------------------------------- +find_package(Gcov REQUIRED) +find_package(Gcovr 4.3 REQUIRED) + +# Variables -------------------------------------------------------------------- +set(COVERAGE_PRODUCTS_DIR ${CMAKE_BINARY_DIR}/coverage + CACHE PATH "Coverage products" +) +set(COVERAGE_REPORT_DIR ${CMAKE_BINARY_DIR}/coverage_results + CACHE PATH "Coverage reports" +) +set(COVERAGE_REPORT_TXT_FILE ${CMAKE_BINARY_DIR}/coverage_results.txt + CACHE FILEPATH "Coverage text report" +) +set(COVERAGE_REPORT_HTML_FILE ${COVERAGE_REPORT_DIR}/index.html + CACHE FILEPATH "Coverage html report" +) + + +set(GCOVR_PARAMS ${COVERAGE_PRODUCTS_DIR} + --root ${CMAKE_SOURCE_DIR} + --gcov-executable ${Gcov_COMMAND} + $<$:--sort-uncovered> + $<$:--sort-percentage> +) + +# Targets ---------------------------------------------------------------------- + +add_custom_target(coverage-clean + COMMAND + ${CMAKE_COMMAND} -E remove -f ${COVERAGE_PRODUCTS_DIR}/*.gcda + COMMAND + ${CMAKE_COMMAND} -E remove -f ${COVERAGE_PRODUCTS_DIR}/*.gcno + COMMAND + ${CMAKE_COMMAND} -E remove -f ${COVERAGE_REPORT_DIR}/*.html + COMMAND + ${CMAKE_COMMAND} -E remove -f ${COVERAGE_REPORT_TXT_FILE} + COMMENT + "${${module_name}_log_prefix} cleaning: code coverage counters and reports" +) + +add_custom_target(coverage-report-notest + COMMAND + ${CMAKE_COMMAND} -E make_directory ${COVERAGE_REPORT_DIR} + COMMAND + ${CMAKE_COMMAND} -E make_directory ${COVERAGE_PRODUCTS_DIR} + COMMAND + find ${CMAKE_BINARY_DIR}/ -type f \"\(\" -name \"*.gcda\" -o -name \"*.gcno\" \"\)\" + -exec cp -u -t ${COVERAGE_PRODUCTS_DIR}/ {} + + COMMAND + ${Gcovr_EXECUTABLE} ${GCOVR_PARAMS} + $<$>:$> + --output ${COVERAGE_REPORT_TXT_FILE} + COMMAND + ${Gcovr_EXECUTABLE} ${GCOVR_PARAMS} --html-details --print-summary + $<$>:$> + --output ${COVERAGE_REPORT_HTML_FILE} + COMMENT + "${${module_name}_log_prefix} processing: code coverage counters and generating report" + DEPENDS + coverage-clean + COMMAND_EXPAND_LISTS +) + +add_custom_target(coverage-report DEPENDS coverage-report-notest) +add_custom_target(coverage DEPENDS coverage-report) + +if(TARGET test) + add_dependencies(coverage-report test) +endif() + +# Functions -------------------------------------------------------------------- +function(target_setup_coverage target) + + # Parameters verification -------------------------------------------------- + if(NOT TARGET ${target}) + message(FATAL_ERROR "${${module_name}_log_prefix} target not found: ${target}") + endif() + + cmake_parse_arguments(_PARAM "" "" "EXCLUDE" ${ARGN}) + if(_PARAM_EXCLUDE) + list(TRANSFORM _PARAM_EXCLUDE PREPEND "\"") + list(TRANSFORM _PARAM_EXCLUDE APPEND "\"") + list(TRANSFORM _PARAM_EXCLUDE PREPEND "--exclude;") + get_target_property(EXCLUDE_PROPERTY coverage-report-notest EXCLUDE) + + if(EXCLUDE_PROPERTY) + list(APPEND EXCLUDE_PROPERTY "${_PARAM_EXCLUDE}") + else() + set(EXCLUDE_PROPERTY "${_PARAM_EXCLUDE}") + endif() + + set_target_properties(coverage-report-notest + PROPERTIES + EXCLUDE "${EXCLUDE_PROPERTY}" + ) + endif() + + get_target_property(TARGET_ALIASED_TARGET ${target} ALIASED_TARGET) + if(TARGET_ALIASED_TARGET) + set(target ${TARGET_ALIASED_TARGET}) + endif() + + message(STATUS "${${module_name}_log_prefix} setup coverage for target: ${target}") + + # Target settings ---------------------------------------------------------- + target_compile_options(${target} + PRIVATE + --coverage + ) + + target_link_libraries(${target} PRIVATE --coverage) +endfunction() + diff --git a/cmake/modules/CommonOptions.cmake b/cmake/modules/CommonOptions.cmake new file mode 100644 index 0000000..a31a57f --- /dev/null +++ b/cmake/modules/CommonOptions.cmake @@ -0,0 +1,53 @@ +include_guard(GLOBAL) + +get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(${module_name}_log_prefix "${module_name}:") + +message(STATUS "${${module_name}_log_prefix} included") + +include(FeatureSummary) + +option(WITH_DOCUMENTING "Generate documentation for all projects" OFF) +add_feature_info([ALL].Documenting WITH_DOCUMENTING + "Generate documentation for all projects" +) + +option(WITH_UNIT_TEST "Build unit tests for all projects" OFF) +add_feature_info([ALL].Unit-tests WITH_UNIT_TEST + "Build unit tests for all projects" +) + +option(WITH_COMPONENT_TEST "Build component tests for all projects" OFF) +add_feature_info([ALL].Component-tests WITH_COMPONENT_TEST + "Build component tests for all projects" +) + +option(WITH_SW_ELEMENT_TEST "Build software element tests for all projects" OFF) +add_feature_info([ALL].Software-Element-tests WITH_SW_ELEMENT_TEST + "Build software element tests for all projects" +) + +option(WITH_COVERAGE "Generate code coverage counters for all projects" OFF) +add_feature_info([ALL].Coverage WITH_COVERAGE + "Generate code coverage counters for all projects" +) + +option(WITH_EXAMPLE "Build examples for all projects" OFF) +add_feature_info([ALL].Example WITH_EXAMPLE + "Build examples for all projects" +) + +option(ENGINEERING_BUILD "Build all projects as engineering variant" OFF) +add_feature_info([ALL].Engineering-build ENGINEERING_BUILD + "Build all projects as engineering variant" +) + +option(WITH_INCLUDE_WHAT_YOU_USE "Build with include-what-you-use globally enabled" OFF) +add_feature_info([GLOBAL].include-what-you-use WITH_INCLUDE_WHAT_YOU_USE + "Build with include-what-you-use globally enabled" +) + +option(WITH_CLANG_TIDY "Build with clang-tidy globally enabled" OFF) +add_feature_info([GLOBAL].clang-tidy WITH_CLANG_TIDY + "Build with clang-tidy globally enabled" +) diff --git a/cmake/modules/UseClangTidy.cmake b/cmake/modules/UseClangTidy.cmake new file mode 100644 index 0000000..1cd1b10 --- /dev/null +++ b/cmake/modules/UseClangTidy.cmake @@ -0,0 +1,62 @@ +include_guard(GLOBAL) + +get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(${module_name}_log_prefix "${module_name}:") + +message(STATUS "${${module_name}_log_prefix} included") + +find_package(ClangTidy REQUIRED) + +get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + +foreach(language C CXX) + if((language IN_LIST enabled_languages) AND (NOT DEFINED CMAKE_${language}_CLANG_TIDY)) + set(CMAKE_${language}_CLANG_TIDY "${ClangTidy_COMMAND}") + endif() +endforeach() + +unset(language) +unset(enabled_languages) + +# produce compilation database +set(CMAKE_EXPORT_COMPILE_COMMANDS true) + +add_custom_target(run-clang-tidy + COMMENT "Static Code Analysis: clang-tidy" +) + +# Functions -------------------------------------------------------------------- +function(target_setup_clang_tidy target) + + # Parameters verification -------------------------------------------------- + if(NOT TARGET ${target}) + message(FATAL_ERROR "${${module_name}_log_prefix} target not found: ${target}") + endif() + + get_target_property(target_aliased_target ${target} ALIASED_TARGET) + if(target_aliased_target) + set(target ${target_aliased_target}) + endif() + + set(clang_tidy_target "clang-tidy-${target}") + if(NOT TARGET clang_tidy_target) + message(STATUS "${${module_name}_log_prefix} setup clang tidy for target: ${target}") + + get_target_property(target_sources ${target} SOURCES) + + add_custom_target(${clang_tidy_target} + COMMAND + "${ClangTidy_COMMAND}" + --quiet + -p "${CMAKE_BINARY_DIR}" + ${target_sources} + DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + VERBATIM + COMMAND_EXPAND_LISTS + COMMENT "Static Code Analysis: clang-tidy: ${target}" + ) + endif() + + add_dependencies(run-clang-tidy ${clang_tidy_target}) +endfunction() \ No newline at end of file diff --git a/cmake/modules/UseGTest.cmake b/cmake/modules/UseGTest.cmake new file mode 100644 index 0000000..c057790 --- /dev/null +++ b/cmake/modules/UseGTest.cmake @@ -0,0 +1,86 @@ +include_guard(GLOBAL) + +if (TARGET test_gtest) # make more strict include guard + return() +endif() + +get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(${module_name}_log_prefix "${module_name}:") + +message(STATUS "${${module_name}_log_prefix} included") + +# declare provided wrapping libraries + +add_library(test_gtest INTERFACE IMPORTED GLOBAL) +add_library(test_gmock INTERFACE IMPORTED GLOBAL) +add_library(test_gtest_main INTERFACE IMPORTED GLOBAL) +add_library(test_gmock_main INTERFACE IMPORTED GLOBAL) + +add_library(test::gtest ALIAS test_gtest) +add_library(test::gmock ALIAS test_gmock) +add_library(test::gtest::main ALIAS test_gtest_main) +add_library(test::gmock::main ALIAS test_gmock_main) + +macro(usegtest_link_if_available lib) + if(TARGET ${lib}) + target_link_libraries(test_${lib} INTERFACE ${lib}) + else() + message(WARNING "${${module_name}_log_prefix} `${lib}` will be not available, it is not included to current build") + endif() +endmacro() + +if(TARGET gtest) # gtest is already included + target_link_libraries(test_gtest INTERFACE gtest) + usegtest_link_if_available(gmock) + usegtest_link_if_available(gtest_main) + usegtest_link_if_available(gmock_main) +else() + find_package(GTest CONFIG) # try to find cmake installed + + if(GTest_FOUND) + if(TARGET GTest::gtest_main) # since cmake 3.20 + target_link_libraries(test_gtest INTERFACE GTest::gtest) + target_link_libraries(test_gmock INTERFACE GTest::gmock) + target_link_libraries(test_gtest_main INTERFACE GTest::gtest_main) + target_link_libraries(test_gmock_main INTERFACE GTest::gmock_main) + elseif(TARGET GTest::Main) # since cmake 3.5 + target_link_libraries(test_gtest INTERFACE GTest::GTest) + target_link_libraries(test_gmock INTERFACE GMock::GMock) + target_link_libraries(test_gtest_main INTERFACE GTest::Main) + target_link_libraries(test_gmock_main INTERFACE GMock::Main) + else() # found but no targets - prior to cmake 3.5 + find_package(Threads REQUIRED) # mandatory + + if(Threads_FOUND) + target_link_libraries(test_gtest INTERFACE ${GTEST_LIBRARIES} Threads::Threads) + target_link_libraries(test_gmock INTERFACE ${GMOCK_LIBRARY} Threads::Threads) + target_link_libraries(test_gtest_main INTERFACE ${GTEST_MAIN_LIBRARIES} Threads::Threads) + target_link_libraries(test_gmock_main INTERFACE ${GMOCK_MAIN_LIBRARY} Threads::Threads) + + foreach(_target gtest gmock) + target_include_directories(test_${_target} INTERFACE ${GTEST_INCLUDE_DIRS}) + target_include_directories(test_${_target}_main INTERFACE ${GTEST_INCLUDE_DIRS}) + endforeach() + endif() + endif() + else() # gtest was not found, we will fetch it and add to build + include(FetchContent) + + FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.11.0 + ) + + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) + endif() + + target_link_libraries(test_gtest INTERFACE gtest) + target_link_libraries(test_gmock INTERFACE gmock) + + target_link_libraries(test_gtest_main INTERFACE gtest_main) + target_link_libraries(test_gmock_main INTERFACE gmock_main) + endif() +endif() diff --git a/cmake/modules/UseIncludeWhatYouUse.cmake b/cmake/modules/UseIncludeWhatYouUse.cmake new file mode 100644 index 0000000..66dc2c7 --- /dev/null +++ b/cmake/modules/UseIncludeWhatYouUse.cmake @@ -0,0 +1,19 @@ +include_guard(GLOBAL) + +get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(${module_name}_log_prefix "${module_name}:") + +message(STATUS "${${module_name}_log_prefix} included") + +find_package(IncludeWhatYouUse REQUIRED) + +get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + +foreach(language C CXX) + if((language IN_LIST enabled_languages) AND (NOT DEFINED CMAKE_${language}_INCLUDE_WHAT_YOU_USE)) + set(CMAKE_${language}_INCLUDE_WHAT_YOU_USE "${IncludeWhatYouUse_COMMAND}") + endif() +endforeach() + +unset(language) +unset(enabled_languages) \ No newline at end of file diff --git a/cmake/templates/crc.pc.in b/cmake/templates/crc.pc.in new file mode 100644 index 0000000..701936d --- /dev/null +++ b/cmake/templates/crc.pc.in @@ -0,0 +1,9 @@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ +URL: @PROJECT_HOMEPAGE_URL@ +Libs: -L${libdir} -l@PROJECT_NAME@ +Cflags: -I${includedir} \ No newline at end of file diff --git a/cmake/templates/crcConfig.cmake.in b/cmake/templates/crcConfig.cmake.in new file mode 100644 index 0000000..05d87e4 --- /dev/null +++ b/cmake/templates/crcConfig.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +#include(CMakeFindDependencyMacro) + +foreach(package_export @PACKAGE_EXPORTS@) + include(${CMAKE_CURRENT_LIST_DIR}/${package_export}Targets.cmake) +endforeach() + +check_required_components(@PROJECT_NAME@) \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..cdd9e16 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,21 @@ +# Requirements to the cmake ---------------------------------------------------- +cmake_minimum_required(VERSION 3.12) + +# Dependency resolving --------------------------------------------------------- +if(NOT TARGET crc::crc) + find_package(crc REQUIRED) +endif() + +# Target Declaration ----------------------------------------------------------- +add_executable(tstcrc) + +# Target Definition ------------------------------------------------------------ +target_sources(tstcrc + PRIVATE + tstcrc.c +) + +target_link_libraries(tstcrc + PRIVATE + crc::crc +) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..63b6c98 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,34 @@ +# Requirements to the cmake ---------------------------------------------------- +cmake_minimum_required(VERSION 3.12) + +# Target Declaration ----------------------------------------------------------- +add_library(crc_common INTERFACE) +add_library(${PACKAGE_NAMESPACE}::common ALIAS crc_common) + +set_target_properties(crc_common + PROPERTIES + EXPORT_NAME common +) + +# Target Definition ------------------------------------------------------------ +target_include_directories(crc_common + INTERFACE + $ + $ +) + +# Installations ---------------------------------------------------------------- +## Targets installation +install(TARGETS crc_common EXPORT commonTargets) + +## CMake's configurations +install(EXPORT commonTargets + NAMESPACE ${PACKAGE_NAMESPACE}:: + DESTINATION ${PACKAGE_INSTALL_CMAKE_DIR} +) + +## Install files +install(FILES checksum.h + DESTINATION ${PACKAGE_INSTALL_INCLUDE_DIR} + COMPONENT library +) diff --git a/precalc/CMakeLists.txt b/precalc/CMakeLists.txt new file mode 100644 index 0000000..e842835 --- /dev/null +++ b/precalc/CMakeLists.txt @@ -0,0 +1,27 @@ +# Requirements to the cmake ---------------------------------------------------- +cmake_minimum_required(VERSION 3.12) + +# Target Declaration ----------------------------------------------------------- +add_executable(prc) + +# Target Definition ------------------------------------------------------------ + +target_sources(prc + PRIVATE + crc32_table.c + crc64_table.c + precalc.c +) + +target_include_directories(prc + PRIVATE + $ +) + +target_link_libraries(prc + PRIVATE + crc::common + crc::compile_flags +) + + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..fca6034 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.12) + +# build all project test utility target ---------------------------------------- +if(NOT TARGET all_unit_tests) + add_custom_target(all_unit_tests + COMMENT "Build all unit tests" + ) +endif() + +add_custom_target(${PROJECT_NAME}_all_unit_tests + COMMENT "Build all ${PROJECT_NAME} unit tests" +) + +add_dependencies(all_unit_tests ${PROJECT_NAME}_all_unit_tests) + +# Target Declaration ----------------------------------------------------------- +add_executable(testall) + +# Target Definition ------------------------------------------------------------ +target_sources(testall + PRIVATE + testnmea.c + testcrc.c + testall.c +) + +target_include_directories(testall + PRIVATE + $ +) + +target_link_libraries(testall + PRIVATE + crc::crc +) + +add_test( + NAME ${PROJECT_NAME}_tesall + COMMAND $ +) + +add_dependencies(${PROJECT_NAME}_all_unit_tests testall) + +# run all project test utility target ------------------------------------------ +if(NOT TARGET run_unit_tests) + add_custom_target(run_unit_tests + COMMENT "Run all unit tests" + ) +endif() + +add_custom_target(run_${PROJECT_NAME}_unit_tests + COMMAND ${CMAKE_CTEST_COMMAND} -j $(nproc) --tests-regex \"${PROJECT_NAME}_*\" --output-on-failure + COMMENT "Run all ${PROJECT_NAME} unit tests" + VERBATIM +) + +add_dependencies(run_unit_tests run_${PROJECT_NAME}_unit_tests) \ No newline at end of file From d3a91808070287f482bbf9ac7b13421447106cfc Mon Sep 17 00:00:00 2001 From: Vitalii Shylienkov Date: Tue, 10 May 2022 14:36:31 -0400 Subject: [PATCH 2/2] Documentation, installation, license --- INSTALL | 126 +++++++ cmake/finders/FindClangTidy.cmake | 105 +++++- cmake/finders/FindGcov.cmake | 384 +++++++++++++++++----- cmake/finders/FindGcovr.cmake | 296 +++++++++++------ cmake/finders/FindIncludeWhatYouUse.cmake | 100 +++++- cmake/modules/CodeCoverage.cmake | 235 ++++++++++--- cmake/modules/CommonOptions.cmake | 99 +++++- cmake/modules/UseClangTidy.cmake | 123 ++++++- cmake/modules/UseGTest.cmake | 86 ----- cmake/modules/UseIncludeWhatYouUse.cmake | 42 ++- cmake/templates/crcConfig.cmake.in | 2 - 11 files changed, 1241 insertions(+), 357 deletions(-) delete mode 100644 cmake/modules/UseGTest.cmake diff --git a/INSTALL b/INSTALL index e45dea2..3481973 100644 --- a/INSTALL +++ b/INSTALL @@ -148,4 +148,130 @@ make clean cleans up the object files, library file and testall executable +CMake Toolchain +=============== + +**libcrc** could be built and installed with CMake, which gives cross-platform +compatibility for all those platform where CMake can work. +Configure process accepts all the standard arguments [cmake(1)](https://cmake.org/cmake/help/latest/manual/cmake.1.html) supports. + +CMake version `>= 3.12` is required. + +Compilers +--------- + +One can test build with different compilers providing them to the configuration step +of CMake in one of the several ways: +- [CMAKE_\_COMPILER](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER.html) + provided + +``` sh +cmake .. -DCMAKE_C_COMPILER=clang +``` + +- toolchain files [CMAKE_TOOLCHAIN_FILE](https://cmake.org/cmake/help/latest/variable/CMAKE_TOOLCHAIN_FILE.html#variable:CMAKE_TOOLCHAIN_FILE) + +```sh +cmake .. --toolchain= +``` + +Generators +---------- + +CMake provides verity of different generators. Underlying generator has to be installed +and available in `$PATH` + +```sh +# for Linux/Unix +cmake .. -G "Unix Makefiles" + +# for Windows +cmake .. -G "Visual Studio 15 2017" +``` + +Options +------- +| Option | Default | Note | +| ------------------------- | ------- | ---------------------------------------------------------- | +| WITH_INCLUDE_WHAT_YOU_USE | OFF | Dev: Validate correctness of `#include` directives [^1] | +| WITH_CLANG_TIDY | OFF | Dev: Run `clang-tidy` static code analysis [^2] | +| WITH_UNIT_TEST | OFF | Build unit tests | +| WITH_COVERAGE | OFF | Dev: Collects coverage metrics during tests execution [^3] | +| WITH_EXAMPLE | OFF | Build examples | + + +Building ``libcrc`` +------------------- + +```sh +mkdir build +cd build +cmake .. +# optionally generator and build type can be specified +# cmake .. -G "ninja" -DCMAKE_BUILD_TYPE=Debug +# build +cmake --build . --config Debug --target all -j 6 +``` + +Building and running tests +-------------------------- + +```sh +mkdir build +cd build +# configure with unit tests +cmake .. -DWITH_UNIT_TEST=On +# build +cmake --build . --config Debug --target all -j 6 +# run tests +cmake -VV +``` + +Installing +---------- + +```sh +mkdir build +cd build +# configure, optionally provide install destination +cmake .. -DCMAKE_INSTALL_PREFIX= +# build and install +cmake --build . --config Debug --target install -j 6 +``` + +Consuming ``libcrc`` in CMake project +------------------------------------- + +If ``libcrc`` is built and installed via CMake toolchain it can be easily discovered +and used within any CMake project + +```cmake +# discover libcrc +find_package(crc REQUIRED) + +add_executable(my_crc_consumer main.c) + +# my_crc_consumer will be linked against libcrc +# and compiled with includes paths from libcrc installation +target_link_libraries(my_crc_consumer + PRIVATE + crc::crc +) +``` + +Consuming via ``pkg-config`` +---------------------------- + +Using CMake toolchain one then can use ``libcrc`` with ``pkg-config`` + +```sh +cc `pkg-config --cflags --libs crc` -o main.c my_crc_consumer +``` + Lammert Bies + +[^1]: requires ``include-what-you-use`` + +[^2]: requires ``clang-tidy`` + +[^3]: requires ``gcov`` or ``llvm-cov`` and ``gcovr`` \ No newline at end of file diff --git a/cmake/finders/FindClangTidy.cmake b/cmake/finders/FindClangTidy.cmake index 6277eb9..361634a 100644 --- a/cmake/finders/FindClangTidy.cmake +++ b/cmake/finders/FindClangTidy.cmake @@ -1,3 +1,79 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +FindClangTidy +------------- + +Locates `clang-tidy `_, +a clang-based C++ “linter” tool. + + +Components +^^^^^^^^^^ + +This module doesn't support components + +Result variables +^^^^^^^^^^^^^^^^ +This module will set the following variables in your project: + + :cmake:variable:`ClangTidy_FOUND`, + :cmake:variable:`CLANGTIDY_FOUND` + + Found ``ClangTidy`` package + + :cmake:variable:`ClangTidy_EXECUTABLE` + + Path to the ``clang-tidy`` executable + + :cmake:variable:`ClangTidy_COMMAND` + + String that has to be used to invoke ``clang-tidy`` + + :cmake:variable:`ClangTidy_VERSION_STRING` + + ``clang-tidy`` full version string + + :cmake:variable:`ClangTidy_VERSION_MAJOR` + + ``clang-tidy`` major version + + :cmake:variable:`ClangTidy_VERSION_MINOR` + + ``clang-tidy`` minor version + + :cmake:variable:`ClangTidy_VERSION_PATCH` + + ``clang-tidy`` patch version + +Hints +^^^^^ + +The following variables may be set to provide hints to this module: + + :cmake:variable:`ClangTidy_DIR`, + :cmake:variable:`CLANGTIDY_DIR` + + Path to the installation root of ``clang-tidy`` + +Environment variables with the same names will be also checked: + + :cmake:envvar:`ClangTidy_DIR`, + :cmake:envvar:`CLANGTIDY_DIR` + +Example usage +^^^^^^^^^^^^^ + +.. code-block:: cmake + + find_package(ClangTidy REQUIRED) + + set(CMAKE_CXX_CLANG_TIDY "${ClangTidy_COMMAND}") + +#]=============================================================================] + # includes --------------------------------------------------------------------- include(FeatureSummary) include(FindPackageHandleStandardArgs) @@ -5,7 +81,7 @@ include(FindPackageHandleStandardArgs) # Internal variables ----------------------------------------------------------- set(cfp_NAME "${CMAKE_FIND_PACKAGE_NAME}") string(TOUPPER "${cfp_NAME}" CFP_NAME) -set(${cfp_NAME}_log_prefix "${cfp_NAME}:") +set(_ClangTidy_log_prefix "${_cf_log_prefix}${cfp_NAME}:" CACHE INTERNAL "FindClangTidy Log prefix") # Declare package properties --------------------------------------------------- set_package_properties(${cfp_NAME} @@ -15,9 +91,9 @@ set_package_properties(${cfp_NAME} ) # Validate find_package() arguments -------------------------------------------- - +# No components supported if(${cfp_NAME}_FIND_COMPONENTS AND NOT ${cfp_NAME}_FIND_QUIETLY) - message(WARNING "${${cfp_NAME}_log_prefix} components not supported") + message(WARNING "${_ClangTidy_log_prefix} components not supported") endif() # Build list of names ---------------------------------------------------------- @@ -69,11 +145,20 @@ unset(suffix) unset(known_file_suffixes) unset(known_file_suffixes_major) +# build list of hints +set(${cfp_NAME}_hints "") +foreach(dir ${cfp_NAME}_DIR ${CFP_NAME}_DIR) + if(DEFINED ${dir}) + list(APPEND ${cfp_NAME}_hints "${${dir}}") + endif() +endforeach() +unset(dir) + # Find binary ------------------------------------------------------------------ find_program(${cfp_NAME}_EXECUTABLE NAMES ${${cfp_NAME}_names} - HINTS ${${cfp_NAME}_DIR} ${${CFP_NAME}_DIR} + HINTS ${${cfp_NAME}_hints} ENV ${cfp_NAME}_DIR ENV ${CFP_NAME}_DIR PATH_SUFFIXES bin @@ -81,26 +166,27 @@ find_program(${cfp_NAME}_EXECUTABLE ) unset(${cfp_NAME}_names) +unset(${cfp_NAME}_hints) # Figure out the version ------------------------------------------------------- if(${cfp_NAME}_EXECUTABLE) - set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}") + set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}" CACHE STRING "") mark_as_advanced(${cfp_NAME}_EXECUTABLE ${cfp_NAME}_COMMAND) execute_process( - COMMAND ${${cfp_NAME}_EXECUTABLE} --version + COMMAND ${${cfp_NAME}_COMMAND} --version RESULT_VARIABLE ${cfp_NAME}_version_result OUTPUT_VARIABLE ${cfp_NAME}_version_output ERROR_VARIABLE ${cfp_NAME}_version_error OUTPUT_STRIP_TRAILING_WHITESPACE ) - + if(${${cfp_NAME}_version_result} EQUAL 0) if(${cfp_NAME}_version_output MATCHES "([0-9]+\.[0-9]+\.[0-9]+)") set(${cfp_NAME}_VERSION_STRING "${CMAKE_MATCH_1}") - + string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" ${cfp_NAME}_VERSION_MAJOR "${${cfp_NAME}_VERSION_STRING}" ) @@ -113,7 +199,7 @@ if(${cfp_NAME}_EXECUTABLE) endif() else() if(NOT ${cfp_NAME}_FIND_QUIETLY) - message(WARNING "${${cfp_NAME}_log_prefix}: version query failed: ${${cfp_NAME}_version_error}") + message(WARNING "${_ClangTidy_log_prefix}: version query failed: ${${cfp_NAME}_version_error}") endif() endif() @@ -131,6 +217,5 @@ find_package_handle_standard_args(${cfp_NAME} ) # clean-up --------------------------------------------------------------------- -unset(${cfp_NAME}_log_prefix) unset(CFP_NAME) unset(cfp_NAME) \ No newline at end of file diff --git a/cmake/finders/FindGcov.cmake b/cmake/finders/FindGcov.cmake index 1eb2c43..8d1c276 100644 --- a/cmake/finders/FindGcov.cmake +++ b/cmake/finders/FindGcov.cmake @@ -1,148 +1,354 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +FindGcov +-------- + +Locate ``gcov``-compatible program: + +- ``gcov`` in case of GNU environment +- ``llvm-cov`` for LLVM environment (can emulate ``gcov``) + +Requirements +^^^^^^^^^^^^ + +- At least one of languages ``C`` ``CXX`` should be enabled for the project + +Components +^^^^^^^^^^ + +This module doesn't support modules + +Result variables +^^^^^^^^^^^^^^^^ +This module will set the following variables in your project: + + :cmake:variable:`Gcov_FOUND`, + :cmake:variable:`GCOV_FOUND` + + Found ``gcov`` package + + :cmake:variable:`Gcov_EXECUTABLE` + + Path to the ``gcov``/``llvm-cov`` executable + + :cmake:variable:`Gcov_COMMAND` + + String that has to be used to call ``gcov`` + + :cmake:variable:`Gcov_VERSION_STRING` + + ``gcov`` full version string + + :cmake:variable:`Gcov_VERSION_MAJOR` + + ``gcov`` major version + + :cmake:variable:`Gcov_VERSION_MINOR` + + ``gcov`` minor version + + :cmake:variable:`Gcov_VERSION_PATCH` + + ``gcov`` version patch + +Hints +^^^^^ + +The following variables may be set to provide hints to this module: + + :cmake:variable:`Gcov_DIR`, + :cmake:variable:`GCOV_DIR` + + Path to the installation root of gcov + +Environment variables with the same names will be also checked: + + :cmake:envvar:`Gcov_DIR`, + :cmake:envvar:`GCOV_DIR` + +Example usage +^^^^^^^^^^^^^ + +.. code-block:: cmake + + find_package(Gcov REQUIRED) + + execute_process( + COMMAND ${Gcov_COMMAND} -version + OUTPUT_VARIABLE Gcov_VERSION_RAW + ERROR_VARIABLE Gcov_VERSION_RAW + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + +#]=============================================================================] + +# includes --------------------------------------------------------------------- include(FeatureSummary) -set_package_properties(Gcov +include(FindPackageHandleStandardArgs) + +# Internal variables ----------------------------------------------------------- +set(cfp_NAME "${CMAKE_FIND_PACKAGE_NAME}") +string(TOUPPER "${cfp_NAME}" CFP_NAME) +set(_Gcov_log_prefix "${_cf_log_prefix}${cfp_NAME}:" CACHE INTERNAL "FindGcov Log prefix") + +# Declare package properties --------------------------------------------------- +set_package_properties(${cfp_NAME} PROPERTIES URL "https://gcc.gnu.org/onlinedocs/gcc/Gcov.html" DESCRIPTION "Tool to test code coverage" ) -macro(find_gnu_gcov_executable) - # potential names: - # gcov - # gcov-x // x - major version of GCC - # ${CROSS_COMPILE}gcov-x - # ${CROSS_COMPILE}gcov +# Validate find_package() arguments -------------------------------------------- +if(${cfp_NAME}_FIND_COMPONENTS AND NOT ${cfp_NAME}_FIND_QUIETLY) + message(WARNING "${_${cfp_NAME}_log_prefix} components not supported") +endif() + +# Helper functions ------------------------------------------------------------- + +#[==[ + Search gcov for GNU environment + + Names to search for (x - major version of GCC): + + ${CROSS_COMPILE}gcov-x # if crosscompilation is enabled and + ${CROSS_COMPILE}gcov # an environment variable CROSS_COMPILE is + # defined + gcov-x + gcov + + Cache Variables + + ``Gcov_DIR`` # may also be set as an environment variable + ``GCOV_DIR`` # may also be set as an environment variable + ``COMPILER_PATH`` + + Result variables + + ``Gcov_EXECUTABLE`` # path to gcov executable + ``Gcov_COMMAND`` # same +#]==] +macro(__gcov_find_gnu_program) + + # get compiler major version string(REGEX MATCH "^[0-9]+" GCC_VERSION_MAJOR "${CMAKE_${LANG}_COMPILER_VERSION}" ) - set(gcov_filenames gcov-${GCC_VERSION_MAJOR} gcov) + # build list of potential names + set(${cfp_NAME}_names gcov gcov-${GCC_VERSION_MAJOR}) - if(DEFINED ENV{CROSS_COMPILE}) - list(PREPEND gcov_filenames $ENV{CROSS_COMPILE}gcov-${GCC_VERSION_MAJOR} $ENV{CROSS_COMPILE}gcov) + if(CMAKE_CROSSCOMPILING AND DEFINED ENV{CROSS_COMPILE}) + list(APPEND ${cfp_NAME}_names + $ENV{CROSS_COMPILE}gcov + $ENV{CROSS_COMPILE}gcov-${GCC_VERSION_MAJOR} + ) endif() + list(REMOVE_DUPLICATES ${cfp_NAME}_names) + list(REVERSE ${cfp_NAME}_names) + # build list of hints # compiler path provided by call site - find_program(Gcov_EXECUTABLE - NAMES ${gcov_filenames} - HINTS "${COMPILER_PATH}" + set(${cfp_NAME}_hints "${COMPILER_PATH}") + list(APPEND ${cfp_NAME}_hints ${__gcc_hints}) + foreach(dir ${cfp_NAME}_DIR ${CFP_NAME}_DIR) + if(DEFINED dir) + list(APPEND ${cfp_NAME}_hints "${dir}") + endif() + endforeach() + list(REMOVE_DUPLICATES ${cfp_NAME}_hints) + unset(dir) + + # look for executable + find_program(${cfp_NAME}_EXECUTABLE + NAMES ${${cfp_NAME}_names} + HINTS ${${cfp_NAME}_hints} + ENV ${cfp_NAME}_DIR + ENV ${CFP_NAME}_DIR + ) + + # clean-up + unset(${cfp_NAME}_hints) + unset(${cfp_NAME}_names) + unset(GCC_VERSION_MAJOR) + + # store in cache + if(${cfp_NAME}_EXECUTABLE) + set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}" CACHE STRING "") + mark_as_advanced(${cfp_NAME}_EXECUTABLE ${cfp_NAME}_COMMAND) + endif() +endmacro() + +#[==[ + Search gcov for LLVM environment + + Names to search for (x - major version of LLVM, y - minor version): + + ${CROSS_COMPILE}llvm-cov-x.y # if crosscompilation is enabled and + ${CROSS_COMPILE}llvm-cov-x # an environment variable CROSS_COMPILE is + ${CROSS_COMPILE}llvm-cov # defined + llvm-cov-x.y + llvm-cov-x + llvm-cov + + Cache Variables + + ``Gcov_DIR`` # may also be set as an environment variable + ``GCOV_DIR`` # may also be set as an environment variable + ``COMPILER_PATH`` + + Result variables + + ``Gcov_EXECUTABLE`` # path to gcov executable + ``Gcov_COMMAND`` # path to script wrapping call to gcov +#]==] +macro(__gcov_find_llvm_program) + # get compiler major and minor versions + string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION_STRING + "${CMAKE_${LANG}_COMPILER_VERSION}" ) - if(Gcov_EXECUTABLE) - set(Gcov_EXECUTABLE "${Gcov_EXECUTABLE}" CACHE FILEPATH "") - set(Gcov_COMMAND "${Gcov_EXECUTABLE}" CACHE STRING "") - mark_as_advanced(Gcov_EXECUTABLE Gcov_COMMAND) + # llvm-cov version < 3.5 not supported + if(LLVM_VERSION_STRING VERSION_GREATER 3.4) + string(REGEX REPLACE "^([0-9]+).[0-9]+" "\\1" LLVM_VERSION_MAJOR + "${LLVM_VERSION_STRING}" + ) + + # build list of potential names + set(${cfp_NAME}_names llvm-cov + llvm-cov-${LLVM_VERSION_MAJOR} + llvm-cov-${LLVM_VERSION_STRING} + ) + + if(CMAKE_CROSSCOMPILING AND DEFINED ENV{CROSS_COMPILE}) + list(APPEND ${cfp_NAME}_names $ENV{CROSS_COMPILE}llvm-cov + $ENV{CROSS_COMPILE}llvm-cov-${LLVM_VERSION_MAJOR} + $ENV{CROSS_COMPILE}llvm-cov-${LLVM_VERSION_STRING} + ) + endif() + list(REMOVE_DUPLICATES ${cfp_NAME}_names) + list(REVERSE ${cfp_NAME}_names) + + # build list of hints + # compiler path provided by call site + set(${cfp_NAME}_hints "${COMPILER_PATH}") + list(APPEND ${cfp_NAME}_hints ${__clang_hints}) + foreach(dir ${cfp_NAME}_DIR ${CFP_NAME}_DIR) + if(DEFINED dir) + list(APPEND ${cfp_NAME}_hints "${dir}") + endif() + endforeach() + list(REMOVE_DUPLICATES ${cfp_NAME}_hints) + unset(dir) + + # look for executable + find_program(${cfp_NAME}_EXECUTABLE + NAMES ${${cfp_NAME}_names} + HINTS ${${cfp_NAME}_hints} + ENV ${cfp_NAME}_DIR + ENV ${CFP_NAME}_DIR + ) + + # clean-up + unset(${cfp_NAME}_hints) + unset(${cfp_NAME}_names) + unset(LLVM_VERSION_MAJOR) + unset(LLVM_VERSION_STRING) + + # store in cache + if(${cfp_NAME}_EXECUTABLE) + # create shell script for gcov command + # llvm-cov gcov - emulates gcov + file(WRITE ${CMAKE_BINARY_DIR}/CMakeFiles/gcov + "#!/bin/bash\nexec ${${cfp_NAME}_EXECUTABLE} gcov \"$@\"" + ) + file(COPY ${CMAKE_BINARY_DIR}/CMakeFiles/gcov + DESTINATION ${CMAKE_BINARY_DIR} + FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ + ) + set(${cfp_NAME}_COMMAND "${CMAKE_BINARY_DIR}/gcov" CACHE STRING "") + mark_as_advanced(${cfp_NAME}_EXECUTABLE ${cfp_NAME}_COMMAND) + endif() endif() endmacro() -get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +#TODO:VSH proper version handling + +# for each enabled language try to find bonary +get_property(ppt_enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) -foreach(LANG ${ENABLED_LANGUAGES}) +foreach(LANG IN LISTS ppt_enabled_languages) # no language if(LANG STREQUAL "NONE") continue() endif() # if Gcov was already found - skip search - if(Gcov_${CMAKE_${LANG}_COMPILER_ID}_EXECUTABLE) + if(${cfp_NAME}_${CMAKE_${LANG}_COMPILER_ID}_EXECUTABLE) continue() endif() # gcov usually placed near to the compiler get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH) - if("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU") - find_gnu_gcov_executable() - elseif("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "^(Apple)?Clang$") - - # potential names: - # llvm-cov - # llvm-cov-x // x - major version of LLVM - # llvm-cov-x.y // x.y - version string of LLVM - string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION_STRING - "${CMAKE_${LANG}_COMPILER_VERSION}" - ) - - string(REGEX REPLACE "^([0-9]+).[0-9]+" "\\1" LLVM_VERSION_MAJOR - "${LLVM_VERSION_STRING}" - ) + # if we are in llvm environment + if(CMAKE_${LANG}_COMPILER_ID MATCHES "^(Apple)?Clang$") + __gcov_find_llvm_program() + endif() - # llvm-cov version < 3.5 not supported - if(LLVM_VERSION_STRING VERSION_GREATER 3.4) - find_program(LLVM_cov_EXECUTABLE - NAMES "llvm-cov-${LLVM_VERSION_STRING}" - "llvm-cov-${LLVM_VERSION_MAJOR}" - "llvm-cov" - HINTS ${COMPILER_PATH} - ) - - if(LLVM_cov_EXECUTABLE) - set(Gcov_EXECUTABLE "${LLVM_cov_EXECUTABLE}" CACHE FILEPATH "") - # llvm-cov gcov - emulates gcov - file(WRITE - ${CMAKE_BINARY_DIR}/CMakeFiles/gcov - "#!/bin/bash\nexec ${LLVM_cov_EXECUTABLE} gcov \"$@\"" - ) - file(COPY ${CMAKE_BINARY_DIR}/CMakeFiles/gcov - DESTINATION ${CMAKE_BINARY_DIR} - FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ - GROUP_EXECUTE GROUP_READ - WORLD_EXECUTE WORLD_READ - ) - set(Gcov_COMMAND "${CMAKE_BINARY_DIR}/gcov" CACHE STRING "") - mark_as_advanced(LLVM_cov_EXECUTABLE Gcov_EXECUTABLE Gcov_COMMAND) - else() - # if nothing found try to find GNU gcov - find_gnu_gcov_executable() - endif() - endif() + # we are in GNU environment or + # llvm-cov was not found - fallback to GNU + if(NOT ${cfp_NAME}_EXECUTABLE) + __gcov_find_gnu_program() endif() - if(Gcov_EXECUTABLE) + if(${cfp_NAME}_EXECUTABLE) # do not repeat search for this compiler anymore - set(Gcov_${CMAKE_${LANG}_COMPILER_ID}_EXECUTABLE "${Gcov_EXECUTABLE}" + set(${cfp_NAME}_${CMAKE_${LANG}_COMPILER_ID}_EXECUTABLE "${${cfp_NAME}_EXECUTABLE}" CACHE FILEPATH "${LANG} gcov binary." ) endif() endforeach() +unset(ppt_enabled_languages) +unset(LANG) -# get and parse version -# exports: -# Gcov_VERSION_STRING -# Gcov_VERSION_MAJOR -# Gcov_VERSION_MINOR -# Gcov_VERSION_PATCH -if(Gcov_EXECUTABLE) +# Figuring out gcov version +if(${cfp_NAME}_EXECUTABLE) execute_process( - COMMAND ${Gcov_COMMAND} -version - OUTPUT_VARIABLE Gcov_VERSION_RAW - ERROR_VARIABLE Gcov_VERSION_RAW + COMMAND ${${cfp_NAME}_COMMAND} -version + OUTPUT_VARIABLE ${cfp_NAME}_VERSION_RAW + ERROR_VARIABLE ${cfp_NAME}_VERSION_RAW OUTPUT_STRIP_TRAILING_WHITESPACE ) - if(Gcov_VERSION_RAW MATCHES "([0-9]+\.[0-9]+\.[0-9]+)") - set(Gcov_VERSION_STRING "${CMAKE_MATCH_1}") + if(${cfp_NAME}_VERSION_RAW MATCHES "([0-9]+\.[0-9]+\.[0-9]+)") + set(${cfp_NAME}_VERSION_STRING "${CMAKE_MATCH_1}") string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" - Gcov_VERSION_MAJOR ${Gcov_VERSION_STRING} + ${cfp_NAME}_VERSION_MAJOR ${${cfp_NAME}_VERSION_STRING} ) string(REGEX REPLACE "[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" - Gcov_VERSION_MINOR ${Gcov_VERSION_STRING} + ${cfp_NAME}_VERSION_MINOR ${${cfp_NAME}_VERSION_STRING} ) string(REGEX REPLACE "[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" - Gcov_VERSION_PATCH ${Gcov_VERSION_STRING} + ${cfp_NAME}_VERSION_PATCH ${${cfp_NAME}_VERSION_STRING} ) endif() endif() +unset(${cfp_NAME}_VERSION_RAW) -# include required Modules -include(FindPackageHandleStandardArgs) # FPHSA to cover flags and version -find_package_handle_standard_args(Gcov - REQUIRED_VARS Gcov_EXECUTABLE - Gcov_COMMAND - VERSION_VAR Gcov_VERSION_STRING +find_package_handle_standard_args(${cfp_NAME} + REQUIRED_VARS ${cfp_NAME}_EXECUTABLE + ${cfp_NAME}_COMMAND + VERSION_VAR ${cfp_NAME}_VERSION_STRING + FAIL_MESSAGE "Install: `sudo apt install gcov`." ) # clean-up -unset(ENABLED_LANGUAGES) -unset(Gcov_VERSION_RAW) +unset(cfp_NAME) +unset(CFP_NAME) diff --git a/cmake/finders/FindGcovr.cmake b/cmake/finders/FindGcovr.cmake index ef2edfa..64e4004 100644 --- a/cmake/finders/FindGcovr.cmake +++ b/cmake/finders/FindGcovr.cmake @@ -1,32 +1,147 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +FindGcovr +--------- + +Locates `Gcovr `_, an utility for managing the use +of the ``GNU`` ``gcov`` utility and generating summarized code coverage results. + + +Components +^^^^^^^^^^ + +This module doesn't support components + +Result variables +^^^^^^^^^^^^^^^^ +This module will set the following variables in your project: + + :cmake:variable:`Gcovr_FOUND`, + :cmake:variable:`GCOVR_FOUND` + + Found ``gcovr`` package + + :cmake:variable:`Gcovr_EXECUTABLE` + + Path to the ``gcovr`` executable + + :cmake:variable:`Gcovr_COMMAND` + + String that has to be used to invoke ``gcovr`` + + :cmake:variable:`Gcovr_DIR` + + Root directory of ``gcovr`` installation + + :cmake:variable:`Gcovr_VERSION_STRING` + + ``gcovr`` full version string + + :cmake:variable:`Gcovr_VERSION_MAJOR` + + ``gcovr`` major version + + :cmake:variable:`Gcovr_VERSION_MINOR` + + ``gcovr`` minor version + +Hints +^^^^^ + +The following variables may be set to provide hints to this module: + + :cmake:variable:`Gcovr_DIR`, + :cmake:variable:`GCOVR_DIR` + + Path to the installation root of ``gcovr`` + +Environment variables with the same names will be also checked: + + :cmake:envvar:`Gcovr_DIR`, + :cmake:envvar:`GCOVR_DIR` + +Example usage +^^^^^^^^^^^^^ + +.. code-block:: cmake + + find_package(Gcovr REQUIRED) + + execute_process( + COMMAND ${Gcovr_COMMAND} + ${CMAKE_BINARY_DIR}/coverage + --root ${CMAKE_SOURCE_DIR} + --gcov-executable ${Gcov_COMMAND} + --output coverage.txt + ) + +#]=============================================================================] + +# includes --------------------------------------------------------------------- include(FeatureSummary) -set_package_properties(Gcovr +include(FindPackageHandleStandardArgs) + +# Internal variables ----------------------------------------------------------- +set(cfp_NAME "${CMAKE_FIND_PACKAGE_NAME}") +string(TOUPPER "${cfp_NAME}" CFP_NAME) +set(_Gcovr_log_prefix "${_cf_log_prefix}${cfp_NAME}:" CACHE INTERNAL "FindGcovr Log prefix") + +# Declare package properties --------------------------------------------------- +set_package_properties(${cfp_NAME} PROPERTIES URL "https://gcovr.com/en/stable/" DESCRIPTION "An utility for managing the use of the GNU gcov utility and generating summarized code coverage results" ) +# Validate find_package() arguments -------------------------------------------- # No components supported -if(Gcovr_FIND_COMPONENTS AND NOT Gcovr_FIND_QUIETLY) - message(STATUS "Find Gcovr: components not supported") +if(${cfp_NAME}_FIND_COMPONENTS AND NOT ${cfp_NAME}_FIND_QUIETLY) + message(WARNING "${_${cfp_NAME}_log_prefix} components not supported") endif() -# Hints where to look for Gcovr executable -# Environment and user variables -set(Gcovr_HINTS "") -foreach(hint Gcovr_DIR GCOVR_DIR) - if(DEFINED ${hint}) - if((EXISTS "${${hint}}") AND (IS_DIRECTORY "${${hint}}")) - list(APPEND Gcovr_HINTS "${${hint}}") - endif() +# Helper functions ------------------------------------------------------------- +macro(__gcovr_parse_shebang _line) + set(_OPTIONS "") + # exclude heading or trailing whitespaces + string(REGEX REPLACE "^ +| +$" "" _line "${_line}") + + if(_line MATCHES "^#!([^ ]*/python.*)") + set(_COMMAND "${CMAKE_MATCH_1}") + if(_COMMAND MATCHES "([^ ]+) (.*)") + set(_COMMAND "${CMAKE_MATCH_1}") + + # options transformed to cmake ;-list + string (REGEX REPLACE " +" ";" _OPTIONS "${CMAKE_MATCH_2}") + endif () + elseif(_line MATCHES "^#!(.*env python.*)$") + set(_COMMAND "${CMAKE_MATCH_1}") + if(_COMMAND MATCHES "([^ ]+) (python[23]?)(.*)") + # command + set(_COMMAND "${CMAKE_MATCH_2}") + + # options transformed to cmake ;-list + string (REGEX REPLACE " +" ";" _OPTIONS "${CMAKE_MATCH_3}") + endif () + else() + set(_COMMAND NOTFOUND) endif() - if(DEFINED ENV{${hint}}) - if((EXISTS "$ENV{${hint}}") AND (IS_DIRECTORY "$ENV{${hint}}")) - list(APPEND Gcovr_HINTS "$ENV{${hint}}") - endif() + unset(_line) +endmacro() + + +# build list of hints +set(${cfp_NAME}_hints "") +foreach(dir ${cfp_NAME}_DIR ${CFP_NAME}_DIR) + if(DEFINED ${dir}) + list(APPEND ${cfp_NAME}_hints "${${dir}}") endif() endforeach() +unset(dir) -# Gcovr can be installed into Python user script directory: +# Gcovr can be installed into Python user script directory: # Unix: ~/.local/bin # Windows: %APPDATA%/Python/Scripts # https://www.python.org/dev/peps/pep-0370 @@ -34,112 +149,105 @@ find_package(Python QUIET COMPONENTS Interpreter) if(Python_Interpreter_FOUND) execute_process( - COMMAND "${Python_EXECUTABLE}" -m site --user-base + COMMAND ${Python_EXECUTABLE} -m site --user-base RESULT_VARIABLE USER_BASE_DIR_RESULT OUTPUT_VARIABLE USER_BASE_DIR_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE ) if(USER_BASE_DIR_RESULT EQUAL 0) - list(APPEND Gcovr_HINTS "${USER_BASE_DIR_OUTPUT}") + list(APPEND ${cfp_NAME}_hints "${USER_BASE_DIR_OUTPUT}") endif() + unset(USER_BASE_DIR_RESULT) + unset(USER_BASE_DIR_OUTPUT) endif() - -find_program(Gcovr_EXECUTABLE - NAMES gcovr - gcovr.py - HINTS ${Gcovr_HINTS} +list(REMOVE_DUPLICATES ${cfp_NAME}_hints) + +# look for executable +find_program(${cfp_NAME}_EXECUTABLE + NAMES gcovr + gcovr.py + HINTS ${cfp_NAME}_hints + ENV ${cfp_NAME}_DIR + ENV ${CFP_NAME}_DIR PATH_SUFFIXES bin Scripts - DOC "The Gcovr executable" + DOC "The ${cfp_NAME} executable" ) +unset(${cfp_NAME}_hints) -if(Gcovr_EXECUTABLE) - mark_as_advanced(Gcovr_EXECUTABLE) +# build up command +if(${cfp_NAME}_EXECUTABLE) + mark_as_advanced(${cfp_NAME}_EXECUTABLE) - file(STRINGS "${Gcovr_EXECUTABLE}" SHEBANG LIMIT_COUNT 1) - - set(Gcovr_PYTHON_COMMAND "${Python_EXECUTABLE}") - set(Gcovr_PYTHON_OPTIONS "") - if(SHEBANG MATCHES "^#!(.*/python.*)$") - # exclude heading or trailing whitespaces - string(REGEX REPLACE "^ +| +$" "" Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}") + file(STRINGS "${${cfp_NAME}_EXECUTABLE}" SHEBANG LIMIT_COUNT 1) - if(Gcovr_PYTHON_COMMAND MATCHES "([^ ]+) (.*)") - # command - set(Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}") + __gcovr_parse_shebang(${SHEBANG}) + unset(SHEBANG) - # options transformed to cmake ;-list - string (REGEX REPLACE " +" ";" Gcovr_PYTHON_OPTIONS "${CMAKE_MATCH_2}") - endif () - elseif(SHEBANG MATCHES "^#!(.*env python.*)$") - # exclude heading or trailing whitespaces - string(REGEX REPLACE "^ +| +$" "" Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}") - - if(Gcovr_PYTHON_COMMAND MATCHES "([^ ]+) (python[23]?) ?(.*)") - # command - set(Gcovr_PYTHON_COMMAND "${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}") + if(_COMMAND) + # escape any special charecter + string(REGEX REPLACE "([.+*?^$])" "\\\\\\1" + _Pythong_EXECUTABLE_ESCEPED "${Python_EXECUTABLE}" + ) - if(CMAKE_MATCH_3) - # options transformed to cmake ;-list - string (REGEX REPLACE " +" ";" Gcovr_PYTHON_OPTIONS "${CMAKE_MATCH_3}") - endif() - endif () - endif() + list(FIND _OPTIONS -E INDEX) + if((INDEX EQUAL -1) AND + (NOT _COMMAND MATCHES "^${_Pythong_EXECUTABLE_ESCEPED}$")) + list(INSERT _OPTIONS 0 -E) + endif() - # escape any special charecter - string(REGEX REPLACE "([.+*?^$])" "\\\\\\1" - _Gcovr_PYTHON_COMMAND_RE "${Python_EXECUTABLE}" - ) + set(${cfp_NAME}_COMMAND "${_COMMAND};${_OPTIONS};${${cfp_NAME}_EXECUTABLE}" CACHE STRING "") - list(FIND Gcovr_PYTHON_OPTIONS -E INDEX) - if((INDEX EQUAL -1) AND - (NOT Gcovr_PYTHON_COMMAND MATCHES "^${_Gcovr_PYTHON_COMMAND_RE}$")) - list(INSERT Gcovr_PYTHON_OPTIONS 0 -E) + unset(_Pythong_EXECUTABLE_ESCEPED) + unset(INDEX) + unset(_COMMAND) + unset(_OPTIONS) + else() + set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}" CACHE STRING "") endif() + mark_as_advanced(${cfp_NAME}_COMMAND) endif() -if(Gcovr_EXECUTABLE) - if(Gcovr_PYTHON_COMMAND) - execute_process( - COMMAND ${Gcovr_PYTHON_COMMAND} ${Gcovr_PYTHON_OPTIONS} "${Gcovr_EXECUTABLE}" --version - OUTPUT_VARIABLE Gcovr_VERSION_RAW - ERROR_VARIABLE Gcovr_VERSION_RAW - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_STRIP_TRAILING_WHITESPACE - ) - elseif(UNIX) - execute_process( - COMMAND "${Gcovr_EXECUTABLE}" --version - OUTPUT_VARIABLE Gcovr_VERSION_RAW - ERROR_VARIABLE Gcovr_VERSION_RAW - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_STRIP_TRAILING_WHITESPACE - ) - endif() +# Figuring out gcovr version +if(${cfp_NAME}_COMMAND) + execute_process( + COMMAND ${${cfp_NAME}_COMMAND} --version + OUTPUT_VARIABLE ${cfp_NAME}_VERSION_RAW + ERROR_VARIABLE ${cfp_NAME}_VERSION_RAW + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE + ) - if(Gcovr_VERSION_RAW MATCHES "gcovr ([.0-9]+)") - set(Gcovr_VERSION_STRING "${CMAKE_MATCH_1}") - - string(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" - Gcovr_VERSION_MAJOR ${Gcovr_VERSION_STRING} + if(${cfp_NAME}_VERSION_RAW MATCHES "gcovr ([.0-9]+)") + set(${cfp_NAME}_VERSION_STRING "${CMAKE_MATCH_1}") + + string(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" + ${cfp_NAME}_VERSION_MAJOR ${${cfp_NAME}_VERSION_STRING} ) - string(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" - Gcovr_VERSION_MINOR ${Gcovr_VERSION_STRING} + string(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" + ${cfp_NAME}_VERSION_MINOR ${${cfp_NAME}_VERSION_STRING} ) endif() endif() +unset(${cfp_NAME}_VERSION_RAW) -if(Gcovr_EXECUTABLE) - get_filename_component(Gcovr_DIR "${Gcovr_EXECUTABLE}" PATH) - string(REGEX REPLACE "/bin/?$" "" Gcovr_DIR "${Gcovr_DIR}") +# Get gcovr installation dir +if(${cfp_NAME}_EXECUTABLE) + get_filename_component(${cfp_NAME}_DIR "${${cfp_NAME}_EXECUTABLE}" PATH) + string(REGEX REPLACE "/bin/?$" "" ${cfp_NAME}_DIR "${${cfp_NAME}_DIR}") # cache it - set(Gcovr_DIR "${Gcovr_DIR}" CACHE PATH "Gcovr installation path") + set(${cfp_NAME}_DIR "${${cfp_NAME}_DIR}" CACHE PATH "${cfp_NAME} installation path") endif() # handle components, version, quiet, required and other flags -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Gcovr - REQUIRED_VARS Gcovr_EXECUTABLE Gcovr_DIR - VERSION_VAR Gcovr_VERSION_STRING +find_package_handle_standard_args(${cfp_NAME} + REQUIRED_VARS ${cfp_NAME}_EXECUTABLE + ${cfp_NAME}_COMMAND + ${cfp_NAME}_DIR + VERSION_VAR ${cfp_NAME}_VERSION_STRING FAIL_MESSAGE "Install: `python3 -m pip install -U gcovr`.\nRemove default: `sudo apt remove gcovr`\n" -) \ No newline at end of file +) + +# clean-up +unset(cfp_NAME) +unset(CFP_NAME) diff --git a/cmake/finders/FindIncludeWhatYouUse.cmake b/cmake/finders/FindIncludeWhatYouUse.cmake index 075e98b..576aefd 100644 --- a/cmake/finders/FindIncludeWhatYouUse.cmake +++ b/cmake/finders/FindIncludeWhatYouUse.cmake @@ -1,3 +1,75 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +FindIncludeWhatYouUse +--------------------- + +Locates `include-what-you-use `_, +a tool for use with clang to analyze #includes in C and C++ source files. + + +Components +^^^^^^^^^^ + +This module doesn't support components + +Result variables +^^^^^^^^^^^^^^^^ +This module will set the following variables in your project: + + :cmake:variable:`IncludeWhatYouUse_FOUND`, + :cmake:variable:`INCLUDEWHATYOUUSE_FOUND` + + Found ``IncludeWhatYouUse`` package + + :cmake:variable:`IncludeWhatYouUse_EXECUTABLE` + + Path to the ``include-what-you-use`` executable + + :cmake:variable:`IncludeWhatYouUse_COMMAND` + + String that has to be used to invoke ``include-what-you-use`` + + :cmake:variable:`IncludeWhatYouUse_VERSION_STRING` + + ``include-what-you-use`` full version string + + :cmake:variable:`IncludeWhatYouUse_VERSION_MAJOR` + + ``include-what-you-use`` major version + + :cmake:variable:`IncludeWhatYouUse_VERSION_MINOR` + + ``include-what-you-use`` minor version + +Hints +^^^^^ + +The following variables may be set to provide hints to this module: + + :cmake:variable:`IncludeWhatYouUse_DIR`, + :cmake:variable:`INCLUDEWHATYOUUSE_DIR` + + Path to the installation root of ``include-what-you-use`` + +Environment variables with the same names will be also checked: + + :cmake:envvar:`IncludeWhatYouUse_DIR`, + :cmake:envvar:`INCLUDEWHATYOUUSE_DIR` + +Example usage +^^^^^^^^^^^^^ + +.. code-block:: cmake + + find_package(IncludeWhatYouUse REQUIRED) + + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${IncludeWhatYouUse_COMMAND}") + +#]=============================================================================] + # includes --------------------------------------------------------------------- include(FeatureSummary) include(FindPackageHandleStandardArgs) @@ -5,7 +77,9 @@ include(FindPackageHandleStandardArgs) # Internal variables ----------------------------------------------------------- set(cfp_NAME "${CMAKE_FIND_PACKAGE_NAME}") string(TOUPPER "${cfp_NAME}" CFP_NAME) -set(${cfp_NAME}_log_prefix "${cfp_NAME}:") +set(_IncludeWhatYouUse_log_prefix "${_cf_log_prefix}${cfp_NAME}:" + CACHE INTERNAL "FindIncludeWhatYouUse Log prefix" +) # Declare package properties --------------------------------------------------- set_package_properties(${cfp_NAME} @@ -15,30 +89,41 @@ set_package_properties(${cfp_NAME} ) # Validate find_package() arguments -------------------------------------------- - +# No components supported if(${cfp_NAME}_FIND_COMPONENTS AND NOT ${cfp_NAME}_FIND_QUIETLY) - message(WARNING "${${cfp_NAME}_log_prefix} components not supported") + message(WARNING "${_IncludeWhatYouUse_log_prefix} components not supported") endif() +# build list of hints +set(${cfp_NAME}_hints "") +foreach(dir ${cfp_NAME}_DIR ${CFP_NAME}_DIR) + if(DEFINED ${dir}) + list(APPEND ${cfp_NAME}_hints "${${dir}}") + endif() +endforeach() +unset(dir) + + # Find binary ------------------------------------------------------------------ find_program(${cfp_NAME}_EXECUTABLE NAMES include-what-you-use - HINTS ${${cfp_NAME}_DIR} ${${CFP_NAME}_DIR} + HINTS ${${cfp_NAME}_hints} ENV ${cfp_NAME}_DIR ENV ${CFP_NAME}_DIR PATH_SUFFIXES bin DOC "The ${cfp_NAME} executable" ) +unset(${cfp_NAME}_hints) # Figure out the version ------------------------------------------------------- if(${cfp_NAME}_EXECUTABLE) - set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}") + set(${cfp_NAME}_COMMAND "${${cfp_NAME}_EXECUTABLE}" CACHE STRING "") mark_as_advanced(${cfp_NAME}_EXECUTABLE ${cfp_NAME}_COMMAND) execute_process( - COMMAND ${${cfp_NAME}_EXECUTABLE} --version + COMMAND ${${cfp_NAME}_COMMAND} --version RESULT_VARIABLE ${cfp_NAME}_version_result OUTPUT_VARIABLE ${cfp_NAME}_version_output ERROR_VARIABLE ${cfp_NAME}_version_error @@ -58,7 +143,7 @@ if(${cfp_NAME}_EXECUTABLE) endif() else() if(NOT ${cfp_NAME}_FIND_QUIETLY) - message(WARNING "${${cfp_NAME}_log_prefix}: version query failed: ${${cfp_NAME}_version_error}") + message(WARNING "${_IncludeWhatYouUse_log_prefix}: version query failed: ${${cfp_NAME}_version_error}") endif() endif() @@ -76,6 +161,5 @@ find_package_handle_standard_args(${cfp_NAME} ) # clean-up --------------------------------------------------------------------- -unset(${cfp_NAME}_log_prefix) unset(CFP_NAME) unset(cfp_NAME) \ No newline at end of file diff --git a/cmake/modules/CodeCoverage.cmake b/cmake/modules/CodeCoverage.cmake index 007e31a..1d4d44b 100644 --- a/cmake/modules/CodeCoverage.cmake +++ b/cmake/modules/CodeCoverage.cmake @@ -1,3 +1,26 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +CodeCoverage +------------ + +This file adds functions and utility targets to collect code coverage statistics +in ``txt`` and ``html`` formats. + +Synopsis +^^^^^^^^ + +One can mark all targets coverage of which is required. To mark target see +:cmake:command:`target_setup_coverage`. After the build step first group of +auxiliary files will be created. Then invoke the test binaries to generate the +second group of auxiliary files. And after use utility target ``coverage-report-notest`` +to generate coverage report. + +#]=============================================================================] + +# include guard ---------------------------------------------------------------- include_guard(GLOBAL) # coverage may be set up from another CodeCoverage.cmake file @@ -7,16 +30,37 @@ if(TARGET coverage) return() endif() -get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) -set(${module_name}_log_prefix "${module_name}:") +get_filename_component(_module_NAME ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(_CodeCoverage_log_prefix "${_cf_log_prefix}${_module_NAME}:" + CACHE INTERNAL "CodeCoverage Log prefix" +) + +message(STATUS "${_CodeCoverage_log_prefix} included") + +#[=============================================================================[.rst: +Options +^^^^^^^ + +This file introduce several options, all of them ``OFF`` by default. -message(STATUS "${${module_name}_log_prefix} included") +- :cmake:option:`COVERAGE_SORT_LINES` + Sort coverage report by lines covered + +- :cmake:option:`COVERAGE_SORT_PERCENTAGE` + + Sort coverage report by percent of coverage + +.. note:: + + Options :cmake:option:`COVERAGE_SORT_LINES` and :cmake:option:`COVERAGE_SORT_PERCENTAGE` + are exclusive and shouldn't be both set at time. +#]=============================================================================] include(FeatureSummary) option(COVERAGE_SORT_LINES "Sort coverage report by lines covered" OFF) -add_feature_info([GLOBAL].CoverageSortedLines - COVERAGE_SORT_LINES +add_feature_info([GLOBAL].CoverageSortedLines + COVERAGE_SORT_LINES "Sort entries by increasing number of uncovered lines." ) @@ -24,50 +68,120 @@ option(COVERAGE_SORT_PERCENTAGE "Sort coverage report by percent of coverage" OFF ) -add_feature_info([GLOBAL].CoverageSortedPercents - COVERAGE_SORT_PERCENTAGE +add_feature_info([GLOBAL].CoverageSortedPercents + COVERAGE_SORT_PERCENTAGE "Sort entries by increasing percentage of uncovered lines." ) +#[=============================================================================[.rst: +Requirements +^^^^^^^^^^^^ + +Requires: + +- ``gcov`` + + Uses :cmake:module:`FindGcov` to obtain ``gcov`` command. + +- ``gcovr`` + + Uses :cmake:module:`FindGcovr` to obtain ``gcovr`` command. + +#]=============================================================================] # Environment validation ------------------------------------------------------- find_package(Gcov REQUIRED) find_package(Gcovr 4.3 REQUIRED) -# Variables -------------------------------------------------------------------- -set(COVERAGE_PRODUCTS_DIR ${CMAKE_BINARY_DIR}/coverage - CACHE PATH "Coverage products" -) -set(COVERAGE_REPORT_DIR ${CMAKE_BINARY_DIR}/coverage_results - CACHE PATH "Coverage reports" -) -set(COVERAGE_REPORT_TXT_FILE ${CMAKE_BINARY_DIR}/coverage_results.txt - CACHE FILEPATH "Coverage text report" -) -set(COVERAGE_REPORT_HTML_FILE ${COVERAGE_REPORT_DIR}/index.html - CACHE FILEPATH "Coverage html report" -) +#[=============================================================================[.rst: +Variables +^^^^^^^^^ + +Client of the module can define next variables to control the behavior: + +- :cmake:variable:`COVERAGE_PRODUCTS_DIR` + + Path to the directory where auxiliary files will be stored + +- :cmake:variable:`COVERAGE_REPORT_DIR` + + Path to the directory where report files will be stored -set(GCOVR_PARAMS ${COVERAGE_PRODUCTS_DIR} +- :cmake:variable:`COVERAGE_REPORT_TXT_FILE` + + Path to the coverage report in ``txt`` format + +- :cmake:variable:`COVERAGE_REPORT_HTML_FILE` + + Path to the coverage report in ``html`` format + +.. note:: + + Some default values will be set if nothing provided. + +#]=============================================================================] +# Variables -------------------------------------------------------------------- +if(NOT DEFINED COVERAGE_PRODUCTS_DIR) + set(COVERAGE_PRODUCTS_DIR ${CMAKE_BINARY_DIR}/coverage) +endif() +if(NOT DEFINED COVERAGE_REPORT_DIR) + set(COVERAGE_REPORT_DIR ${CMAKE_BINARY_DIR}/coverage_results) +endif() +if(NOT DEFINED COVERAGE_REPORT_TXT_FILE) + set(COVERAGE_REPORT_TXT_FILE ${COVERAGE_REPORT_DIR}/coverage_results.txt) +endif() +if(NOT DEFINED COVERAGE_REPORT_HTML_FILE) + set(COVERAGE_REPORT_HTML_FILE ${COVERAGE_REPORT_DIR}/index.html) +endif() + +set(Gcovr_PARAMS ${COVERAGE_PRODUCTS_DIR} --root ${CMAKE_SOURCE_DIR} --gcov-executable ${Gcov_COMMAND} $<$:--sort-uncovered> $<$:--sort-percentage> ) -# Targets ---------------------------------------------------------------------- +#[=============================================================================[.rst: +Utility Targets +^^^^^^^^^^^^^^^ + +Introduces targets: + +- ``coverage-clean`` + + Run clean-up to wipe the results of the coverage analysis, including auxiliary + files. + +- ``coverage-report-notest`` + + Collect code coverage counters without running all the tests. + +- ``coverage-report`` + + Collect code coverage counters, running all the tests beforehand. +.. note:: + + To run tests utility target ``test`` will be used. In case it is defined. + +- ``coverage`` + + Alias to ``coverage-report`` + +#]=============================================================================] + +# Targets ---------------------------------------------------------------------- add_custom_target(coverage-clean - COMMAND + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_PRODUCTS_DIR}/*.gcda - COMMAND + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_PRODUCTS_DIR}/*.gcno - COMMAND + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_REPORT_DIR}/*.html - COMMAND + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_REPORT_TXT_FILE} COMMENT - "${${module_name}_log_prefix} cleaning: code coverage counters and reports" + "${_CodeCoverage_log_prefix} cleaning: code coverage counters and reports" ) add_custom_target(coverage-report-notest @@ -76,19 +190,19 @@ add_custom_target(coverage-report-notest COMMAND ${CMAKE_COMMAND} -E make_directory ${COVERAGE_PRODUCTS_DIR} COMMAND - find ${CMAKE_BINARY_DIR}/ -type f \"\(\" -name \"*.gcda\" -o -name \"*.gcno\" \"\)\" + find ${CMAKE_BINARY_DIR}/ -type f \"\(\" -name \"*.gcda\" -o -name \"*.gcno\" \"\)\" -exec cp -u -t ${COVERAGE_PRODUCTS_DIR}/ {} + COMMAND - ${Gcovr_EXECUTABLE} ${GCOVR_PARAMS} + ${Gcovr_EXECUTABLE} ${Gcovr_PARAMS} $<$>:$> --output ${COVERAGE_REPORT_TXT_FILE} COMMAND - ${Gcovr_EXECUTABLE} ${GCOVR_PARAMS} --html-details --print-summary + ${Gcovr_EXECUTABLE} ${Gcovr_PARAMS} --html-details --print-summary $<$>:$> --output ${COVERAGE_REPORT_HTML_FILE} COMMENT - "${${module_name}_log_prefix} processing: code coverage counters and generating report" - DEPENDS + "${_CodeCoverage_log_prefix} processing: code coverage counters and generating report" + DEPENDS coverage-clean COMMAND_EXPAND_LISTS ) @@ -100,46 +214,61 @@ if(TARGET test) add_dependencies(coverage-report test) endif() +#[=============================================================================[.rst: +Commands +^^^^^^^^ + +.. cmake:command:: target_setup_coverage + +Marks target to be analyzed with code coverage + +.. code-block:: cmake + + target_setup_coverage( [EXCLUDE [ ...]) + +This command set needed compilation and linkage flags to the target ````. + + ``EXCLUDE`` + + Allows to set regular expressions to exclude directories or files from + coverage analysis. + +#]=============================================================================] # Functions -------------------------------------------------------------------- function(target_setup_coverage target) # Parameters verification -------------------------------------------------- if(NOT TARGET ${target}) - message(FATAL_ERROR "${${module_name}_log_prefix} target not found: ${target}") + message(FATAL_ERROR "${_CodeCoverage_log_prefix} target not found: ${target}") endif() + # Find out un-aliased name of the target + get_target_property(TARGET_ALIASED_TARGET ${target} ALIASED_TARGET) + if(TARGET_ALIASED_TARGET) + set(target ${TARGET_ALIASED_TARGET}) + endif() + + # parse EXCLUDE option cmake_parse_arguments(_PARAM "" "" "EXCLUDE" ${ARGN}) if(_PARAM_EXCLUDE) list(TRANSFORM _PARAM_EXCLUDE PREPEND "\"") list(TRANSFORM _PARAM_EXCLUDE APPEND "\"") list(TRANSFORM _PARAM_EXCLUDE PREPEND "--exclude;") - get_target_property(EXCLUDE_PROPERTY coverage-report-notest EXCLUDE) - if(EXCLUDE_PROPERTY) - list(APPEND EXCLUDE_PROPERTY "${_PARAM_EXCLUDE}") - else() - set(EXCLUDE_PROPERTY "${_PARAM_EXCLUDE}") - endif() - - set_target_properties(coverage-report-notest - PROPERTIES - EXCLUDE "${EXCLUDE_PROPERTY}" + set_property(TARGET coverage-report-notest + APPEND + PROPERTY + EXCLUDE ${_PARAM_EXCLUDE} ) endif() - get_target_property(TARGET_ALIASED_TARGET ${target} ALIASED_TARGET) - if(TARGET_ALIASED_TARGET) - set(target ${TARGET_ALIASED_TARGET}) - endif() - message(STATUS "${${module_name}_log_prefix} setup coverage for target: ${target}") + message(STATUS "${_CodeCoverage_log_prefix} setup coverage for target: ${target}") # Target settings ---------------------------------------------------------- - target_compile_options(${target} - PRIVATE - --coverage - ) - + target_compile_options(${target} PRIVATE --coverage) target_link_libraries(${target} PRIVATE --coverage) endfunction() +# clean-up --------------------------------------------------------------------- +unset(_module_NAME) diff --git a/cmake/modules/CommonOptions.cmake b/cmake/modules/CommonOptions.cmake index a31a57f..72fae08 100644 --- a/cmake/modules/CommonOptions.cmake +++ b/cmake/modules/CommonOptions.cmake @@ -1,52 +1,145 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +CommonOptions +------------- + +This file introduces set of commonly used options which project may utilize. + +.. note:: + + All options are ``Off`` by default. + +Options +^^^^^^^ +#]=============================================================================] + +# include guard ---------------------------------------------------------------- include_guard(GLOBAL) -get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) -set(${module_name}_log_prefix "${module_name}:") +get_filename_component(_module_NAME ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(_CommonOptions_log_prefix "${_cf_log_prefix}${_module_NAME}:" + CACHE INTERNAL "CommonOptions Log prefix" +) -message(STATUS "${${module_name}_log_prefix} included") +message(STATUS "${_CommonOptions_log_prefix} included") include(FeatureSummary) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_DOCUMENTING` + + Generate documentation for all projects + +#]=============================================================================] + option(WITH_DOCUMENTING "Generate documentation for all projects" OFF) add_feature_info([ALL].Documenting WITH_DOCUMENTING "Generate documentation for all projects" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_UNIT_TEST` + + Build unit tests for all projects + +#]=============================================================================] + option(WITH_UNIT_TEST "Build unit tests for all projects" OFF) add_feature_info([ALL].Unit-tests WITH_UNIT_TEST "Build unit tests for all projects" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_COMPONENT_TEST` + + Build component tests for all projects + +#]=============================================================================] + option(WITH_COMPONENT_TEST "Build component tests for all projects" OFF) add_feature_info([ALL].Component-tests WITH_COMPONENT_TEST "Build component tests for all projects" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_SW_ELEMENT_TEST` + + Build software element tests for all projects + +#]=============================================================================] + option(WITH_SW_ELEMENT_TEST "Build software element tests for all projects" OFF) add_feature_info([ALL].Software-Element-tests WITH_SW_ELEMENT_TEST "Build software element tests for all projects" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_COVERAGE` + + Generate code coverage counters for all projects + +#]=============================================================================] + option(WITH_COVERAGE "Generate code coverage counters for all projects" OFF) add_feature_info([ALL].Coverage WITH_COVERAGE "Generate code coverage counters for all projects" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_EXAMPLE` + + Build examples for all projects + +#]=============================================================================] + option(WITH_EXAMPLE "Build examples for all projects" OFF) add_feature_info([ALL].Example WITH_EXAMPLE "Build examples for all projects" ) +#[=============================================================================[.rst: + +- :cmake:variable:`ENGINEERING_BUILD` + + Build all projects as engineering variant + +#]=============================================================================] + option(ENGINEERING_BUILD "Build all projects as engineering variant" OFF) add_feature_info([ALL].Engineering-build ENGINEERING_BUILD "Build all projects as engineering variant" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_INCLUDE_WHAT_YOU_USE` + + Build with include-what-you-use globally enabled + +#]=============================================================================] + option(WITH_INCLUDE_WHAT_YOU_USE "Build with include-what-you-use globally enabled" OFF) add_feature_info([GLOBAL].include-what-you-use WITH_INCLUDE_WHAT_YOU_USE "Build with include-what-you-use globally enabled" ) +#[=============================================================================[.rst: + +- :cmake:variable:`WITH_CLANG_TIDY` + + Build with clang-tidy globally enabled + +#]=============================================================================] + option(WITH_CLANG_TIDY "Build with clang-tidy globally enabled" OFF) add_feature_info([GLOBAL].clang-tidy WITH_CLANG_TIDY "Build with clang-tidy globally enabled" diff --git a/cmake/modules/UseClangTidy.cmake b/cmake/modules/UseClangTidy.cmake index 1cd1b10..8198284 100644 --- a/cmake/modules/UseClangTidy.cmake +++ b/cmake/modules/UseClangTidy.cmake @@ -1,10 +1,63 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +UseClangTidy +------------ + +This file provides support for `clang-tidy `_. +See `CMAKE__CLANG_TIDY `_ +for details. + +Synopsis +^^^^^^^^ + +Two main ways to generate static code analysis: + +- in-build generation + + During build step of a project clang-tidy check every C/C++ source file. + +.. warning:: + + CMake will compile source code with ``clang`` which emulates the behavior + of the compiler CMake detects by default or provided via toolchain files or + configuration step parameters. + +- out-of-build generation + + Using command :cmake:command:`target_setup_clang_tidy` to mark those target for which + ``clang-tidy`` has to run and then use custom target ``run-clang-tidy``. + +.. warning:: + + Files generated in build time will not exist and may lead to analysis errors. + +#]=============================================================================] + + +# include guard ---------------------------------------------------------------- include_guard(GLOBAL) -get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) -set(${module_name}_log_prefix "${module_name}:") +get_filename_component(_module_NAME ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(_UseClangTidy_log_prefix "${_cf_log_prefix}${_module_NAME}:" + CACHE INTERNAL "UseClangTidy Log prefix" +) + +message(STATUS "${_UseClangTidy_log_prefix} included") -message(STATUS "${${module_name}_log_prefix} included") +#[=============================================================================[.rst: +Requirements +^^^^^^^^^^^^ + +Requires: + +- ``clang-tidy`` + + Uses :cmake:module:`FindClangTidy` to obtain ``clang-tidy`` command. +#]=============================================================================] find_package(ClangTidy REQUIRED) get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) @@ -19,44 +72,96 @@ unset(language) unset(enabled_languages) # produce compilation database +#[=============================================================================[.rst: +Compilation database +^^^^^^^^^^^^^^^^^^^^ +.. note:: + + For producing static code analysis diagnostics in out-of-build variant compilation + database will be generated. + +#]=============================================================================] set(CMAKE_EXPORT_COMPILE_COMMANDS true) +#[=============================================================================[.rst: +Custom Targets +^^^^^^^^^^^^^^ + +Introduces targets: + +- ``run-clang-tidy`` + + Global target to run ``clang-tidy`` in out-of-build variant. + +.. code-block:: shell + + # configure step + cmake .. -DWITH_CLANG_TIDY=ON + cmake --build . --target run-clang-tidy + +#]=============================================================================] add_custom_target(run-clang-tidy COMMENT "Static Code Analysis: clang-tidy" ) +#[=============================================================================[.rst: +Commands +^^^^^^^^ + +.. cmake:command:: target_setup_clang_tidy + +Marks target to be analyzed by ``clang-tidy`` in out-of-build variant:: + + target_setup_clang_tidy() + +This command collects sources for the target ```` and create custom +target ``clang-tidy-`` in order to run ``clang-tidy`` for this source +files. This target will be run in scope of execution ``run-clang-tidy`` target. + +#]=============================================================================] # Functions -------------------------------------------------------------------- function(target_setup_clang_tidy target) # Parameters verification -------------------------------------------------- if(NOT TARGET ${target}) - message(FATAL_ERROR "${${module_name}_log_prefix} target not found: ${target}") + message(FATAL_ERROR "${_UseClangTidy_log_prefix} target not found: ${target}") endif() + # Find out un-aliased name of the target get_target_property(target_aliased_target ${target} ALIASED_TARGET) if(target_aliased_target) set(target ${target_aliased_target}) endif() + unset(target_aliased_target) + # Prevent duplication of the target names set(clang_tidy_target "clang-tidy-${target}") if(NOT TARGET clang_tidy_target) - message(STATUS "${${module_name}_log_prefix} setup clang tidy for target: ${target}") + message(STATUS "${_UseClangTidy_log_prefix} setup clang tidy for target: ${target}") + # collect target sources get_target_property(target_sources ${target} SOURCES) + list(FILTER target_sources EXCLUDE REGEX ".*\.hpp") + # create custom target to analyze sources add_custom_target(${clang_tidy_target} COMMAND "${ClangTidy_COMMAND}" --quiet -p "${CMAKE_BINARY_DIR}" ${target_sources} - DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json ${target_sources} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} VERBATIM COMMAND_EXPAND_LISTS COMMENT "Static Code Analysis: clang-tidy: ${target}" - ) + ) + unset(target_sources) endif() add_dependencies(run-clang-tidy ${clang_tidy_target}) -endfunction() \ No newline at end of file + unset(clang_tidy_target) +endfunction() + +# clean-up --------------------------------------------------------------------- +unset(_module_NAME) diff --git a/cmake/modules/UseGTest.cmake b/cmake/modules/UseGTest.cmake deleted file mode 100644 index c057790..0000000 --- a/cmake/modules/UseGTest.cmake +++ /dev/null @@ -1,86 +0,0 @@ -include_guard(GLOBAL) - -if (TARGET test_gtest) # make more strict include guard - return() -endif() - -get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) -set(${module_name}_log_prefix "${module_name}:") - -message(STATUS "${${module_name}_log_prefix} included") - -# declare provided wrapping libraries - -add_library(test_gtest INTERFACE IMPORTED GLOBAL) -add_library(test_gmock INTERFACE IMPORTED GLOBAL) -add_library(test_gtest_main INTERFACE IMPORTED GLOBAL) -add_library(test_gmock_main INTERFACE IMPORTED GLOBAL) - -add_library(test::gtest ALIAS test_gtest) -add_library(test::gmock ALIAS test_gmock) -add_library(test::gtest::main ALIAS test_gtest_main) -add_library(test::gmock::main ALIAS test_gmock_main) - -macro(usegtest_link_if_available lib) - if(TARGET ${lib}) - target_link_libraries(test_${lib} INTERFACE ${lib}) - else() - message(WARNING "${${module_name}_log_prefix} `${lib}` will be not available, it is not included to current build") - endif() -endmacro() - -if(TARGET gtest) # gtest is already included - target_link_libraries(test_gtest INTERFACE gtest) - usegtest_link_if_available(gmock) - usegtest_link_if_available(gtest_main) - usegtest_link_if_available(gmock_main) -else() - find_package(GTest CONFIG) # try to find cmake installed - - if(GTest_FOUND) - if(TARGET GTest::gtest_main) # since cmake 3.20 - target_link_libraries(test_gtest INTERFACE GTest::gtest) - target_link_libraries(test_gmock INTERFACE GTest::gmock) - target_link_libraries(test_gtest_main INTERFACE GTest::gtest_main) - target_link_libraries(test_gmock_main INTERFACE GTest::gmock_main) - elseif(TARGET GTest::Main) # since cmake 3.5 - target_link_libraries(test_gtest INTERFACE GTest::GTest) - target_link_libraries(test_gmock INTERFACE GMock::GMock) - target_link_libraries(test_gtest_main INTERFACE GTest::Main) - target_link_libraries(test_gmock_main INTERFACE GMock::Main) - else() # found but no targets - prior to cmake 3.5 - find_package(Threads REQUIRED) # mandatory - - if(Threads_FOUND) - target_link_libraries(test_gtest INTERFACE ${GTEST_LIBRARIES} Threads::Threads) - target_link_libraries(test_gmock INTERFACE ${GMOCK_LIBRARY} Threads::Threads) - target_link_libraries(test_gtest_main INTERFACE ${GTEST_MAIN_LIBRARIES} Threads::Threads) - target_link_libraries(test_gmock_main INTERFACE ${GMOCK_MAIN_LIBRARY} Threads::Threads) - - foreach(_target gtest gmock) - target_include_directories(test_${_target} INTERFACE ${GTEST_INCLUDE_DIRS}) - target_include_directories(test_${_target}_main INTERFACE ${GTEST_INCLUDE_DIRS}) - endforeach() - endif() - endif() - else() # gtest was not found, we will fetch it and add to build - include(FetchContent) - - FetchContent_Declare(googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.11.0 - ) - - FetchContent_GetProperties(googletest) - if(NOT googletest_POPULATED) - FetchContent_Populate(googletest) - add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) - endif() - - target_link_libraries(test_gtest INTERFACE gtest) - target_link_libraries(test_gmock INTERFACE gmock) - - target_link_libraries(test_gtest_main INTERFACE gtest_main) - target_link_libraries(test_gmock_main INTERFACE gmock_main) - endif() -endif() diff --git a/cmake/modules/UseIncludeWhatYouUse.cmake b/cmake/modules/UseIncludeWhatYouUse.cmake index 66dc2c7..793caae 100644 --- a/cmake/modules/UseIncludeWhatYouUse.cmake +++ b/cmake/modules/UseIncludeWhatYouUse.cmake @@ -1,10 +1,45 @@ +# Author: Vitalii Shylienkov +# License: MIT +# Copyright: (c) 2018-2022 Vitalii Shylienkov + +#[=============================================================================[.rst: +UseIncludeWhatYouUse +-------------------- + +This file provides support for `include-what-you-use `_. +See `CMAKE__INCLUDE_WHAT_YOU_USE +`_ +for details. + +Synopsis +^^^^^^^^ + +Pre-compile step will be added to each compilation run to validate the set of +included files + +#]=============================================================================] + +# include guard ---------------------------------------------------------------- include_guard(GLOBAL) -get_filename_component(module_name ${CMAKE_CURRENT_LIST_FILE} NAME_WE) -set(${module_name}_log_prefix "${module_name}:") +get_filename_component(_module_NAME ${CMAKE_CURRENT_LIST_FILE} NAME_WE) +set(_UseIncludeWhatYouUse_log_prefix "${_cf_log_prefix}${_module_NAME}:" + CACHE INTERNAL "UseIncludeWhatYouUse Log prefix" +) + +message(STATUS "${_UseIncludeWhatYouUse_log_prefix} included") + +#[=============================================================================[.rst: +Requirements +^^^^^^^^^^^^ + +Requires: -message(STATUS "${${module_name}_log_prefix} included") +- ``include-what-you-use`` + Uses :cmake:module:`FindIncludeWhatYouUse` to obtain ``include-what-you-use`` + command. +#]=============================================================================] find_package(IncludeWhatYouUse REQUIRED) get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES) @@ -15,5 +50,6 @@ foreach(language C CXX) endif() endforeach() +# clean-up --------------------------------------------------------------------- unset(language) unset(enabled_languages) \ No newline at end of file diff --git a/cmake/templates/crcConfig.cmake.in b/cmake/templates/crcConfig.cmake.in index 05d87e4..abe2d9a 100644 --- a/cmake/templates/crcConfig.cmake.in +++ b/cmake/templates/crcConfig.cmake.in @@ -1,7 +1,5 @@ @PACKAGE_INIT@ -#include(CMakeFindDependencyMacro) - foreach(package_export @PACKAGE_EXPORTS@) include(${CMAKE_CURRENT_LIST_DIR}/${package_export}Targets.cmake) endforeach()