1# This file is part of CMake-codecov. 2# 3# Copyright (c) 4# 2015-2017 RWTH Aachen University, Federal Republic of Germany 5# 6# See the LICENSE file in the package base directory for details 7# 8# Written by Alexander Haase, alexander.haase@rwth-aachen.de 9# 10 11 12# configuration 13set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data") 14set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init") 15set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture") 16set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html") 17 18 19 20 21# Search for Gcov which is used by Lcov. 22find_package(Gcov) 23 24 25 26 27# This function will add lcov evaluation for target <TNAME>. Only sources of 28# this target will be evaluated and no dependencies will be added. It will call 29# geninfo on any source file of <TNAME> once and store the info file in the same 30# directory. 31# 32# Note: This function is only a wrapper to define this function always, even if 33# coverage is not supported by the compiler or disabled. This function must 34# be defined here, because the module will be exited, if there is no coverage 35# support by the compiler or it is disabled by the user. 36function (add_lcov_target TNAME) 37 if (LCOV_FOUND) 38 # capture initial coverage data 39 lcov_capture_initial_tgt(${TNAME}) 40 41 # capture coverage data after execution 42 lcov_capture_tgt(${TNAME}) 43 endif () 44endfunction (add_lcov_target) 45 46 47 48 49# include required Modules 50include(FindPackageHandleStandardArgs) 51 52# Search for required lcov binaries. 53find_program(LCOV_BIN lcov) 54find_program(GENINFO_BIN geninfo) 55find_program(GENHTML_BIN genhtml) 56find_package_handle_standard_args(lcov 57 REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN 58) 59 60# enable genhtml C++ demangeling, if c++filt is found. 61set(GENHTML_CPPFILT_FLAG "") 62find_program(CPPFILT_BIN c++filt) 63if (NOT CPPFILT_BIN STREQUAL "") 64 set(GENHTML_CPPFILT_FLAG "--demangle-cpp") 65endif (NOT CPPFILT_BIN STREQUAL "") 66 67# enable no-external flag for lcov, if available. 68if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG) 69 set(FLAG "") 70 execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP) 71 string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}") 72 if (GENINFO_RES) 73 set(FLAG "--no-external") 74 endif () 75 76 set(GENINFO_EXTERN_FLAG "${FLAG}" 77 CACHE STRING "Geninfo flag to exclude system sources.") 78endif () 79 80# If Lcov was not found, exit module now. 81if (NOT LCOV_FOUND) 82 return() 83endif (NOT LCOV_FOUND) 84 85 86 87 88# Create directories to be used. 89file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT}) 90file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE}) 91 92set(LCOV_REMOVE_PATTERNS "") 93 94# This function will merge lcov files to a single target file. Additional lcov 95# flags may be set with setting LCOV_EXTRA_FLAGS before calling this function. 96function (lcov_merge_files OUTFILE ...) 97 # Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files. 98 list(REMOVE_AT ARGV 0) 99 100 # Generate merged file. 101 string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}") 102 add_custom_command(OUTPUT "${OUTFILE}.raw" 103 COMMAND cat ${ARGV} > ${OUTFILE}.raw 104 DEPENDS ${ARGV} 105 COMMENT "Generating ${FILE_REL}" 106 ) 107 108 add_custom_command(OUTPUT "${OUTFILE}" 109 COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE} 110 --base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS} 111 COMMAND ${LCOV_BIN} --quiet -r ${OUTFILE} ${LCOV_REMOVE_PATTERNS} 112 --output-file ${OUTFILE} ${LCOV_EXTRA_FLAGS} 113 DEPENDS ${OUTFILE}.raw 114 COMMENT "Post-processing ${FILE_REL}" 115 ) 116endfunction () 117 118 119 120 121# Add a new global target to generate initial coverage reports for all targets. 122# This target will be used to generate the global initial info file, which is 123# used to gather even empty report data. 124if (NOT TARGET lcov-capture-init) 125 add_custom_target(lcov-capture-init) 126 set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "") 127endif (NOT TARGET lcov-capture-init) 128 129 130# This function will add initial capture of coverage data for target <TNAME>, 131# which is needed to get also data for objects, which were not loaded at 132# execution time. It will call geninfo for every source file of <TNAME> once and 133# store the info file in the same directory. 134function (lcov_capture_initial_tgt TNAME) 135 # We don't have to check, if the target has support for coverage, thus this 136 # will be checked by add_coverage_target in Findcoverage.cmake. Instead we 137 # have to determine which gcov binary to use. 138 get_target_property(TSOURCES ${TNAME} SOURCES) 139 set(SOURCES "") 140 set(TCOMPILER "") 141 foreach (FILE ${TSOURCES}) 142 codecov_path_of_source(${FILE} FILE) 143 if (NOT "${FILE}" STREQUAL "") 144 codecov_lang_of_source(${FILE} LANG) 145 if (NOT "${LANG}" STREQUAL "") 146 list(APPEND SOURCES "${FILE}") 147 set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) 148 endif () 149 endif () 150 endforeach () 151 152 # If no gcov binary was found, coverage data can't be evaluated. 153 if (NOT GCOV_${TCOMPILER}_BIN) 154 message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") 155 return() 156 endif () 157 158 set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") 159 set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") 160 161 162 set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) 163 set(GENINFO_FILES "") 164 foreach(FILE ${SOURCES}) 165 # generate empty coverage files 166 set(OUTFILE "${TDIR}/${FILE}.info.init") 167 list(APPEND GENINFO_FILES ${OUTFILE}) 168 169 add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN} 170 --quiet --base-directory ${PROJECT_SOURCE_DIR} --initial 171 --gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE} 172 ${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno 173 DEPENDS ${TNAME} 174 COMMENT "Capturing initial coverage data for ${FILE}" 175 ) 176 endforeach() 177 178 # Concatenate all files generated by geninfo to a single file per target. 179 set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info") 180 set(LCOV_EXTRA_FLAGS "--initial") 181 lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) 182 add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE}) 183 184 # add geninfo file generation to global lcov-geninfo target 185 add_dependencies(lcov-capture-init ${TNAME}-capture-init) 186 set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}" 187 "${OUTFILE}" CACHE INTERNAL "" 188 ) 189endfunction (lcov_capture_initial_tgt) 190 191 192# This function will generate the global info file for all targets. It has to be 193# called after all other CMake functions in the root CMakeLists.txt file, to get 194# a full list of all targets that generate coverage data. 195function (lcov_capture_initial) 196 # Skip this function (and do not create the following targets), if there are 197 # no input files. 198 if ("${LCOV_CAPTURE_INIT_FILES}" STREQUAL "") 199 return() 200 endif () 201 202 # Add a new target to merge the files of all targets. 203 set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info") 204 lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES}) 205 add_custom_target(lcov-geninfo-init ALL DEPENDS ${OUTFILE} 206 lcov-capture-init 207 ) 208endfunction (lcov_capture_initial) 209 210 211 212 213# Add a new global target to generate coverage reports for all targets. This 214# target will be used to generate the global info file. 215if (NOT TARGET lcov-capture) 216 add_custom_target(lcov-capture) 217 set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "") 218endif (NOT TARGET lcov-capture) 219 220 221# This function will add capture of coverage data for target <TNAME>, which is 222# needed to get also data for objects, which were not loaded at execution time. 223# It will call geninfo for every source file of <TNAME> once and store the info 224# file in the same directory. 225function (lcov_capture_tgt TNAME) 226 # We don't have to check, if the target has support for coverage, thus this 227 # will be checked by add_coverage_target in Findcoverage.cmake. Instead we 228 # have to determine which gcov binary to use. 229 get_target_property(TSOURCES ${TNAME} SOURCES) 230 set(SOURCES "") 231 set(TCOMPILER "") 232 foreach (FILE ${TSOURCES}) 233 codecov_path_of_source(${FILE} FILE) 234 if (NOT "${FILE}" STREQUAL "") 235 codecov_lang_of_source(${FILE} LANG) 236 if (NOT "${LANG}" STREQUAL "") 237 list(APPEND SOURCES "${FILE}") 238 set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) 239 endif () 240 endif () 241 endforeach () 242 243 # If no gcov binary was found, coverage data can't be evaluated. 244 if (NOT GCOV_${TCOMPILER}_BIN) 245 message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") 246 return() 247 endif () 248 249 set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") 250 set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") 251 252 253 set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) 254 set(GENINFO_FILES "") 255 foreach(FILE ${SOURCES}) 256 # Generate coverage files. If no .gcda file was generated during 257 # execution, the empty coverage file will be used instead. 258 set(OUTFILE "${TDIR}/${FILE}.info") 259 list(APPEND GENINFO_FILES ${OUTFILE}) 260 261 add_custom_command(OUTPUT ${OUTFILE} 262 COMMAND test -f "${TDIR}/${FILE}.gcda" 263 && ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory 264 ${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN} 265 --output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG} 266 ${TDIR}/${FILE}.gcda 267 || cp ${OUTFILE}.init ${OUTFILE} 268 DEPENDS ${TNAME} ${TNAME}-capture-init 269 COMMENT "Capturing coverage data for ${FILE}" 270 ) 271 endforeach() 272 273 # Concatenate all files generated by geninfo to a single file per target. 274 set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info") 275 lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) 276 add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE}) 277 278 # add geninfo file generation to global lcov-capture target 279 add_dependencies(lcov-capture ${TNAME}-geninfo) 280 set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL 281 "" 282 ) 283 284 # Add target for generating html output for this target only. 285 file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME}) 286 add_custom_target(${TNAME}-genhtml 287 COMMAND ${GENHTML_BIN} --quiet --sort --prefix ${PROJECT_SOURCE_DIR} 288 --baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info 289 --output-directory ${LCOV_HTML_PATH}/${TNAME} 290 --title "${CMAKE_PROJECT_NAME} - target ${TNAME}" 291 ${GENHTML_CPPFILT_FLAG} ${OUTFILE} 292 DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init 293 ) 294endfunction (lcov_capture_tgt) 295 296 297# This function will generate the global info file for all targets. It has to be 298# called after all other CMake functions in the root CMakeLists.txt file, to get 299# a full list of all targets that generate coverage data. 300function (lcov_capture) 301 # Skip this function (and do not create the following targets), if there are 302 # no input files. 303 if ("${LCOV_CAPTURE_FILES}" STREQUAL "") 304 return() 305 endif () 306 307 # Add a new target to merge the files of all targets. 308 set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info") 309 lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES}) 310 add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture) 311 312 # Add a new global target for all lcov targets. This target could be used to 313 # generate the lcov html output for the whole project instead of calling 314 # <TARGET>-geninfo and <TARGET>-genhtml for each target. It will also be 315 # used to generate a html site for all project data together instead of one 316 # for each target. 317 if (NOT TARGET lcov) 318 file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/all_targets) 319 add_custom_target(lcov 320 COMMAND ${GENHTML_BIN} --quiet --sort 321 --baseline-file ${LCOV_DATA_PATH_INIT}/all_targets.info 322 --output-directory ${LCOV_HTML_PATH}/all_targets 323 --title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}" 324 ${GENHTML_CPPFILT_FLAG} ${OUTFILE} 325 DEPENDS lcov-geninfo-init lcov-geninfo 326 ) 327 endif () 328endfunction (lcov_capture) 329 330 331 332 333# Add a new global target to generate the lcov html report for the whole project 334# instead of calling <TARGET>-genhtml for each target (to create an own report 335# for each target). Instead of the lcov target it does not require geninfo for 336# all targets, so you have to call <TARGET>-geninfo to generate the info files 337# the targets you'd like to have in your report or lcov-geninfo for generating 338# info files for all targets before calling lcov-genhtml. 339file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets) 340if (NOT TARGET lcov-genhtml) 341 add_custom_target(lcov-genhtml 342 COMMAND ${GENHTML_BIN} 343 --quiet 344 --output-directory ${LCOV_HTML_PATH}/selected_targets 345 --title \"${CMAKE_PROJECT_NAME} - targets `find 346 ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name 347 \"all_targets.info\" -exec basename {} .info \\\;`\" 348 --prefix ${PROJECT_SOURCE_DIR} 349 --sort 350 ${GENHTML_CPPFILT_FLAG} 351 `find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name 352 \"all_targets.info\"` 353 ) 354endif (NOT TARGET lcov-genhtml) 355