[coverage] Added cmake files for coverage
authorGuillaume Jacquenot <guillaume.jacquenot@gmail.com>
Tue, 21 Feb 2017 20:19:25 +0000 (21:19 +0100)
committerGuillaume Jacquenot <guillaume.jacquenot@gmail.com>
Tue, 21 Feb 2017 20:19:25 +0000 (21:19 +0100)
CMAKE/FindGcov.cmake [new file with mode: 0644]
CMAKE/Findcodecov.cmake [new file with mode: 0644]

diff --git a/CMAKE/FindGcov.cmake b/CMAKE/FindGcov.cmake
new file mode 100644 (file)
index 0000000..715da6e
--- /dev/null
@@ -0,0 +1,157 @@
+# This file is part of CMake-codecov.
+#
+# Copyright (c)
+#   2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# See the LICENSE file in the package base directory for details
+#
+# Written by Alexander Haase, alexander.haase@rwth-aachen.de
+#
+
+
+# include required Modules
+include(FindPackageHandleStandardArgs)
+
+
+# Search for gcov binary.
+set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
+set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY})
+
+get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+foreach (LANG ${ENABLED_LANGUAGES})
+       # Gcov evaluation is dependend on the used compiler. Check gcov support for
+       # each compiler that is used. If gcov binary was already found for this
+       # compiler, do not try to find it again.
+       if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN)
+               get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH)
+
+               if ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU")
+                       # Some distributions like OSX (homebrew) ship gcov with the compiler
+                       # version appended as gcov-x. To find this binary we'll build the
+                       # suggested binary name with the compiler version.
+                       string(REGEX MATCH "^[0-9]+" GCC_VERSION
+                               "${CMAKE_${LANG}_COMPILER_VERSION}")
+
+                       find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov
+                               HINTS ${COMPILER_PATH})
+
+               elseif ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "Clang")
+                       # Some distributions like Debian ship llvm-cov with the compiler
+                       # version appended as llvm-cov-x.y. To find this binary we'll build
+                       # the suggested binary name with the compiler version.
+                       string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION
+                               "${CMAKE_${LANG}_COMPILER_VERSION}")
+
+                       # llvm-cov prior version 3.5 seems to be not working with coverage
+                       # evaluation tools, but these versions are compatible with the gcc
+                       # gcov tool.
+                       if(LLVM_VERSION VERSION_GREATER 3.4)
+                               find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_VERSION}"
+                                       "llvm-cov" HINTS ${COMPILER_PATH})
+                               mark_as_advanced(LLVM_COV_BIN)
+
+                               if (LLVM_COV_BIN)
+                                       find_program(LLVM_COV_WRAPPER "llvm-cov-wrapper" PATHS
+                                               ${CMAKE_MODULE_PATH})
+                                       if (LLVM_COV_WRAPPER)
+                                               set(GCOV_BIN "${LLVM_COV_WRAPPER}" CACHE FILEPATH "")
+
+                                               # set additional parameters
+                                               set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV
+                                                       "LLVM_COV_BIN=${LLVM_COV_BIN}" CACHE STRING
+                                                       "Environment variables for llvm-cov-wrapper.")
+                                               mark_as_advanced(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV)
+                                       endif ()
+                               endif ()
+                       endif ()
+
+                       if (NOT GCOV_BIN)
+                               # Fall back to gcov binary if llvm-cov was not found or is
+                               # incompatible. This is the default on OSX, but may crash on
+                               # recent Linux versions.
+                               find_program(GCOV_BIN gcov HINTS ${COMPILER_PATH})
+                       endif ()
+               endif ()
+
+
+               if (GCOV_BIN)
+                       set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN "${GCOV_BIN}" CACHE STRING
+                               "${LANG} gcov binary.")
+
+                       if (NOT CMAKE_REQUIRED_QUIET)
+                               message("-- Found gcov evaluation for "
+                               "${CMAKE_${LANG}_COMPILER_ID}: ${GCOV_BIN}")
+                       endif()
+
+                       unset(GCOV_BIN CACHE)
+               endif ()
+       endif ()
+endforeach ()
+
+
+
+
+# Add a new global target for all gcov targets. This target could be used to
+# generate the gcov files for the whole project instead of calling <TARGET>-gcov
+# for each target.
+if (NOT TARGET gcov)
+       add_custom_target(gcov)
+endif (NOT TARGET gcov)
+
+
+
+# This function will add gcov evaluation for target <TNAME>. Only sources of
+# this target will be evaluated and no dependencies will be added. It will call
+# Gcov on any source file of <TNAME> once and store the gcov file in the same
+# directory.
+function (add_gcov_target TNAME)
+       set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
+
+       # We don't have to check, if the target has support for coverage, thus this
+       # will be checked by add_coverage_target in Findcoverage.cmake. Instead we
+       # have to determine which gcov binary to use.
+       get_target_property(TSOURCES ${TNAME} SOURCES)
+       set(SOURCES "")
+       set(TCOMPILER "")
+       foreach (FILE ${TSOURCES})
+               codecov_path_of_source(${FILE} FILE)
+               if (NOT "${FILE}" STREQUAL "")
+                       codecov_lang_of_source(${FILE} LANG)
+                       if (NOT "${LANG}" STREQUAL "")
+                               list(APPEND SOURCES "${FILE}")
+                               set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
+                       endif ()
+               endif ()
+       endforeach ()
+
+       # If no gcov binary was found, coverage data can't be evaluated.
+       if (NOT GCOV_${TCOMPILER}_BIN)
+               message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
+               return()
+       endif ()
+
+       set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
+       set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")
+
+
+       set(BUFFER "")
+       foreach(FILE ${SOURCES})
+               get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH)
+
+               # call gcov
+               add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov
+                       COMMAND ${GCOV_ENV} ${GCOV_BIN} ${TDIR}/${FILE}.gcno > /dev/null
+                       DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno
+                       WORKING_DIRECTORY ${FILE_PATH}
+               )
+
+               list(APPEND BUFFER ${TDIR}/${FILE}.gcov)
+       endforeach()
+
+
+       # add target for gcov evaluation of <TNAME>
+       add_custom_target(${TNAME}-gcov DEPENDS ${BUFFER})
+
+       # add evaluation target to the global gcov target.
+       add_dependencies(gcov ${TNAME}-gcov)
+endfunction (add_gcov_target)
diff --git a/CMAKE/Findcodecov.cmake b/CMAKE/Findcodecov.cmake
new file mode 100644 (file)
index 0000000..1125078
--- /dev/null
@@ -0,0 +1,256 @@
+# This file is part of CMake-codecov.
+#
+# Copyright (c)
+#   2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# See the LICENSE file in the package base directory for details
+#
+# Written by Alexander Haase, alexander.haase@rwth-aachen.de
+#
+
+
+# Add an option to choose, if coverage should be enabled or not. If enabled
+# marked targets will be build with coverage support and appropriate targets
+# will be added. If disabled coverage will be ignored for *ALL* targets.
+option(ENABLE_COVERAGE "Enable coverage build." OFF)
+
+set(COVERAGE_FLAG_CANDIDATES
+       # gcc and clang
+       "-O0 -g -fprofile-arcs -ftest-coverage"
+
+       # gcc and clang fallback
+       "-O0 -g --coverage"
+)
+
+
+# To avoid error messages about CMP0051, this policy will be set to new. There
+# will be no problem, as TARGET_OBJECTS generator expressions will be filtered
+# with a regular expression from the sources.
+if (POLICY CMP0051)
+       cmake_policy(SET CMP0051 NEW)
+endif()
+
+
+# Add coverage support for target ${TNAME} and register target for coverage
+# evaluation. If coverage is disabled or not supported, this function will
+# simply do nothing.
+#
+# Note: This function is only a wrapper to define this function always, even if
+#   coverage is not supported by the compiler or disabled. This function must
+#   be defined here, because the module will be exited, if there is no coverage
+#   support by the compiler or it is disabled by the user.
+function (add_coverage TNAME)
+       # only add coverage for target, if coverage is support and enabled.
+       if (ENABLE_COVERAGE)
+               foreach (TNAME ${ARGV})
+                       add_coverage_target(${TNAME})
+               endforeach ()
+       endif ()
+endfunction (add_coverage)
+
+
+# Add global target to gather coverage information after all targets have been
+# added. Other evaluation functions could be added here, after checks for the
+# specific module have been passed.
+#
+# Note: This function is only a wrapper to define this function always, even if
+#   coverage is not supported by the compiler or disabled. This function must
+#   be defined here, because the module will be exited, if there is no coverage
+#   support by the compiler or it is disabled by the user.
+function (coverage_evaluate)
+       # add lcov evaluation
+       if (LCOV_FOUND)
+               lcov_capture_initial()
+               lcov_capture()
+       endif (LCOV_FOUND)
+endfunction ()
+
+
+# Exit this module, if coverage is disabled. add_coverage is defined before this
+# return, so this module can be exited now safely without breaking any build-
+# scripts.
+if (NOT ENABLE_COVERAGE)
+       return()
+endif ()
+
+
+
+
+# Find the reuired flags foreach language.
+set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
+set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY})
+
+get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+foreach (LANG ${ENABLED_LANGUAGES})
+       # Coverage flags are not dependend on language, but the used compiler. So
+       # instead of searching flags foreach language, search flags foreach compiler
+       # used.
+       set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
+       if (NOT COVERAGE_${COMPILER}_FLAGS)
+               foreach (FLAG ${COVERAGE_FLAG_CANDIDATES})
+                       if(NOT CMAKE_REQUIRED_QUIET)
+                               message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]")
+                       endif()
+
+                       set(CMAKE_REQUIRED_FLAGS "${FLAG}")
+                       unset(COVERAGE_FLAG_DETECTED CACHE)
+
+                       if (${LANG} STREQUAL "C")
+                               include(CheckCCompilerFlag)
+                               check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED)
+
+                       elseif (${LANG} STREQUAL "CXX")
+                               include(CheckCXXCompilerFlag)
+                               check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED)
+
+                       elseif (${LANG} STREQUAL "Fortran")
+                               # CheckFortranCompilerFlag was introduced in CMake 3.x. To be
+                               # compatible with older Cmake versions, we will check if this
+                               # module is present before we use it. Otherwise we will define
+                               # Fortran coverage support as not available.
+                               include(CheckFortranCompilerFlag OPTIONAL
+                                       RESULT_VARIABLE INCLUDED)
+                               if (INCLUDED)
+                                       check_fortran_compiler_flag("${FLAG}"
+                                               COVERAGE_FLAG_DETECTED)
+                               elseif (NOT CMAKE_REQUIRED_QUIET)
+                                       message("-- Performing Test COVERAGE_FLAG_DETECTED")
+                                       message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed"
+                                               " (Check not supported)")
+                               endif ()
+                       endif()
+
+                       if (COVERAGE_FLAG_DETECTED)
+                               set(COVERAGE_${COMPILER}_FLAGS "${FLAG}"
+                                       CACHE STRING "${COMPILER} flags for code coverage.")
+                               mark_as_advanced(COVERAGE_${COMPILER}_FLAGS)
+                               break()
+                       endif ()
+               endforeach ()
+       endif ()
+endforeach ()
+
+set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})
+
+
+
+
+# Helper function to get the language of a source file.
+function (codecov_lang_of_source FILE RETURN_VAR)
+       get_filename_component(FILE_EXT "${FILE}" EXT)
+       string(TOLOWER "${FILE_EXT}" FILE_EXT)
+       string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
+
+       get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+       foreach (LANG ${ENABLED_LANGUAGES})
+               list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
+               if (NOT ${TEMP} EQUAL -1)
+                       set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
+                       return()
+               endif ()
+       endforeach()
+
+       set(${RETURN_VAR} "" PARENT_SCOPE)
+endfunction ()
+
+
+# Helper function to get the relative path of the source file destination path.
+# This path is needed by FindGcov and FindLcov cmake files to locate the
+# captured data.
+function (codecov_path_of_source FILE RETURN_VAR)
+       string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE})
+
+       # If expression was found, SOURCEFILE is a generator-expression for an
+       # object library. Currently we found no way to call this function automatic
+       # for the referenced target, so it must be called in the directoryso of the
+       # object library definition.
+       if (NOT "${_source}" STREQUAL "")
+               set(${RETURN_VAR} "" PARENT_SCOPE)
+               return()
+       endif ()
+
+
+       string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}")
+       if(IS_ABSOLUTE ${FILE})
+               file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE})
+       endif()
+
+       # get the right path for file
+       string(REPLACE ".." "__" PATH "${FILE}")
+
+       set(${RETURN_VAR} "${PATH}" PARENT_SCOPE)
+endfunction()
+
+
+
+
+# Add coverage support for target ${TNAME} and register target for coverage
+# evaluation.
+function(add_coverage_target TNAME)
+       # Check if all sources for target use the same compiler. If a target uses
+       # e.g. C and Fortran mixed and uses different compilers (e.g. clang and
+       # gfortran) this can trigger huge problems, because different compilers may
+       # use different implementations for code coverage.
+       get_target_property(TSOURCES ${TNAME} SOURCES)
+       set(TARGET_COMPILER "")
+       set(ADDITIONAL_FILES "")
+       foreach (FILE ${TSOURCES})
+               # If expression was found, FILE is a generator-expression for an object
+               # library. Object libraries will be ignored.
+               string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
+               if ("${_file}" STREQUAL "")
+                       codecov_lang_of_source(${FILE} LANG)
+                       if (LANG)
+                               list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID})
+
+                               list(APPEND ADDITIONAL_FILES "${FILE}.gcno")
+                               list(APPEND ADDITIONAL_FILES "${FILE}.gcda")
+                       endif ()
+               endif ()
+       endforeach ()
+
+       list(REMOVE_DUPLICATES TARGET_COMPILER)
+       list(LENGTH TARGET_COMPILER NUM_COMPILERS)
+
+       if (NUM_COMPILERS GREATER 1)
+               message(AUTHOR_WARNING "Coverage disabled for target ${TNAME} because "
+                       "it will be compiled by different compilers.")
+               return()
+
+       elseif ((NUM_COMPILERS EQUAL 0) OR
+               (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS"))
+               message(AUTHOR_WARNING "Coverage disabled for target ${TNAME} "
+                       "because there is no sanitizer available for target sources.")
+               return()
+       endif()
+
+
+       # enable coverage for target
+       set_property(TARGET ${TNAME} APPEND_STRING
+               PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}")
+       set_property(TARGET ${TNAME} APPEND_STRING
+               PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}")
+
+
+       # Add gcov files generated by compiler to clean target.
+       set(CLEAN_FILES "")
+       foreach (FILE ${ADDITIONAL_FILES})
+               codecov_path_of_source(${FILE} FILE)
+               list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}")
+       endforeach()
+
+       set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
+               "${CLEAN_FILES}")
+
+
+       add_gcov_target(${TNAME})
+       add_lcov_target(${TNAME})
+endfunction(add_coverage_target)
+
+
+
+
+# Include modules for parsing the collected data and output it in a readable
+# format (like gcov and lcov).
+find_package(Gcov)
+find_package(Lcov)