1# 2# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15 16# USAGE: To enable any code coverage instrumentation/targets, the single CMake 17# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or 18# on the command line. 19# 20# From this point, there are two primary methods for adding instrumentation to 21# targets: 1 - A blanket instrumentation by calling `add_code_coverage()`, where 22# all targets in that directory and all subdirectories are automatically 23# instrumented. 2 - Per-target instrumentation by calling 24# `target_code_coverage(<TARGET_NAME>)`, where the target is given and thus only 25# that target is instrumented. This applies to both libraries and executables. 26# 27# To add coverage targets, such as calling `make ccov` to generate the actual 28# coverage information for perusal or consumption, call 29# `target_code_coverage(<TARGET_NAME>)` on an *executable* target. 30# 31# Example 1: All targets instrumented 32# 33# In this case, the coverage information reported will will be that of the 34# `theLib` library target and `theExe` executable. 35# 36# 1a: Via global command 37# 38# ~~~ 39# add_code_coverage() # Adds instrumentation to all targets 40# 41# add_library(theLib lib.cpp) 42# 43# add_executable(theExe main.cpp) 44# target_link_libraries(theExe PRIVATE theLib) 45# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports. 46# ~~~ 47# 48# 1b: Via target commands 49# 50# ~~~ 51# add_library(theLib lib.cpp) 52# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. 53# 54# add_executable(theExe main.cpp) 55# target_link_libraries(theExe PRIVATE theLib) 56# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. 57# ~~~ 58# 59# Example 2: Target instrumented, but with regex pattern of files to be excluded 60# from report 61# 62# ~~~ 63# add_executable(theExe main.cpp non_covered.cpp) 64# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. 65# ~~~ 66# 67# Example 3: Target added to the 'ccov' and 'ccov-all' targets 68# 69# ~~~ 70# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. 71# 72# add_executable(theExe main.cpp non_covered.cpp) 73# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. 74# ~~~ 75 76# Options 77# option( 78# CODE_COVERAGE 79# "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" 80# OFF) 81 82# Programs 83find_program(LLVM_COV_PATH llvm-cov) 84find_program(LLVM_PROFDATA_PATH llvm-profdata) 85find_program(LCOV_PATH lcov) 86find_program(GENHTML_PATH genhtml) 87# Hide behind the 'advanced' mode flag for GUI/ccmake 88mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) 89 90# Variables 91set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) 92set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) 93 94# Common initialization/checks 95if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) 96 set(CODE_COVERAGE_ADDED ON) 97 98 # Common Targets 99 add_custom_target( 100 ccov-preprocessing 101 COMMAND ${CMAKE_COMMAND} -E make_directory 102 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} 103 DEPENDS ccov-clean) 104 105 if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 106 OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 107 # Messages 108 message(STATUS "Building with llvm Code Coverage Tools") 109 110 if(NOT LLVM_COV_PATH) 111 message(FATAL_ERROR "llvm-cov not found! Aborting.") 112 else() 113 # Version number checking for 'EXCLUDE' compatibility 114 execute_process(COMMAND ${LLVM_COV_PATH} --version 115 OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) 116 string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION 117 ${LLVM_COV_VERSION_CALL_OUTPUT}) 118 119 if(LLVM_COV_VERSION VERSION_LESS "7.0.0") 120 message( 121 WARNING 122 "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" 123 ) 124 endif() 125 endif() 126 127 # Targets 128 if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 129 add_custom_target( 130 ccov-clean 131 COMMAND ${CMAKE_COMMAND} -E remove -f 132 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 133 COMMAND ${CMAKE_COMMAND} -E remove -f 134 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) 135 else() 136 add_custom_target( 137 ccov-clean 138 COMMAND ${CMAKE_COMMAND} -E rm -f 139 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 140 COMMAND ${CMAKE_COMMAND} -E rm -f 141 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) 142 endif() 143 144 # Used to get the shared object file list before doing the main all- 145 # processing 146 add_custom_target( 147 ccov-libs 148 COMMAND ; 149 COMMENT "libs ready for coverage report.") 150 151 elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 152 "GNU") 153 # Messages 154 message(STATUS "Building with lcov Code Coverage Tools") 155 156 if(CMAKE_BUILD_TYPE) 157 string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) 158 if(NOT ${upper_build_type} STREQUAL "DEBUG") 159 message( 160 WARNING 161 "Code coverage results with an optimized (non-Debug) build may be misleading" 162 ) 163 endif() 164 else() 165 message( 166 WARNING 167 "Code coverage results with an optimized (non-Debug) build may be misleading" 168 ) 169 endif() 170 if(NOT LCOV_PATH) 171 message(FATAL_ERROR "lcov not found! Aborting...") 172 endif() 173 if(NOT GENHTML_PATH) 174 message(FATAL_ERROR "genhtml not found! Aborting...") 175 endif() 176 177 # Targets 178 add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory 179 ${CMAKE_BINARY_DIR} --zerocounters) 180 181 else() 182 message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") 183 endif() 184endif() 185 186# Adds code coverage instrumentation to a library, or instrumentation/targets 187# for an executable target. 188# ~~~ 189# EXECUTABLE ADDED TARGETS: 190# GCOV/LCOV: 191# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. 192# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. 193# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. 194# 195# LLVM-COV: 196# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. 197# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. 198# ccov-${TARGET_NAME} : Generates HTML code coverage report. 199# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. 200# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. 201# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. 202# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. 203# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. 204# ccov-all-export : Exports the coverage report to a JSON file. 205# 206# Required: 207# TARGET_NAME - Name of the target to generate code coverage for. 208# Optional: 209# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. 210# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. 211# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. 212# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. 213# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory 214# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. 215# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** 216# OBJECTS <TARGETS> - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output 217# ARGS <ARGUMENTS> - For executables ONLY, appends the given arguments to the associated ccov-* executable call 218# ~~~ 219function(target_code_coverage TARGET_NAME) 220 # Argument parsing 221 set(options AUTO ALL EXTERNAL PUBLIC INTERFACE) 222 set(single_value_keywords COVERAGE_TARGET_NAME) 223 set(multi_value_keywords EXCLUDE OBJECTS ARGS) 224 cmake_parse_arguments( 225 target_code_coverage "${options}" "${single_value_keywords}" 226 "${multi_value_keywords}" ${ARGN}) 227 228 # Set the visibility of target functions to PUBLIC, INTERFACE or default to 229 # PRIVATE. 230 if(target_code_coverage_PUBLIC) 231 set(TARGET_VISIBILITY PUBLIC) 232 elseif(target_code_coverage_INTERFACE) 233 set(TARGET_VISIBILITY INTERFACE) 234 else() 235 set(TARGET_VISIBILITY PRIVATE) 236 endif() 237 238 if(NOT target_code_coverage_COVERAGE_TARGET_NAME) 239 # If a specific name was given, use that instead. 240 set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) 241 endif() 242 243 if(CODE_COVERAGE) 244 245 # Add code coverage instrumentation to the target's linker command 246 if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 247 OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 248 target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} 249 -fprofile-instr-generate -fcoverage-mapping) 250 target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} 251 -fprofile-instr-generate -fcoverage-mapping) 252 elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 253 "GNU") 254 target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs 255 -ftest-coverage) 256 target_link_libraries(${TARGET_NAME} ${TARGET_VISIBILITY} gcov) 257 endif() 258 259 # Targets 260 get_target_property(target_type ${TARGET_NAME} TYPE) 261 262 # Add shared library to processing for 'all' targets 263 if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) 264 if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 265 OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 266 add_custom_target( 267 ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} 268 COMMAND 269 ${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>" >> 270 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 271 DEPENDS ccov-preprocessing ${TARGET_NAME}) 272 273 if(NOT TARGET ccov-libs) 274 message( 275 FATAL_ERROR 276 "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." 277 ) 278 endif() 279 280 add_dependencies(ccov-libs 281 ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) 282 endif() 283 endif() 284 285 # For executables add targets to run and produce output 286 if(target_type STREQUAL "EXECUTABLE") 287 if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 288 OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 289 290 # If there are shared objects to also work with, generate the string to 291 # add them here 292 foreach(SO_TARGET ${target_code_coverage_OBJECTS}) 293 # Check to see if the target is a shared object 294 if(TARGET ${SO_TARGET}) 295 get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) 296 if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") 297 set(SO_OBJECTS ${SO_OBJECTS} -object=$<TARGET_FILE:${SO_TARGET}>) 298 endif() 299 endif() 300 endforeach() 301 302 # Run the executable, generating raw profile data Make the run data 303 # available for further processing. Separated to allow Windows to run 304 # this target serially. 305 add_custom_target( 306 ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} 307 COMMAND 308 ${CMAKE_COMMAND} -E env 309 LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw 310 $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS} 311 COMMAND 312 ${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>" 313 ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list 314 COMMAND 315 ${CMAKE_COMMAND} -E echo 316 "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" 317 >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list 318 JOB_POOL ccov_serial_pool 319 DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) 320 321 # Merge the generated profile data so llvm-cov can process it 322 add_custom_target( 323 ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} 324 COMMAND 325 ${LLVM_PROFDATA_PATH} merge -sparse 326 ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o 327 ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 328 DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) 329 330 # Ignore regex only works on LLVM >= 7 331 if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") 332 foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) 333 set(EXCLUDE_REGEX ${EXCLUDE_REGEX} 334 -ignore-filename-regex='${EXCLUDE_ITEM}') 335 endforeach() 336 endif() 337 338 # Print out details of the coverage information to the command line 339 add_custom_target( 340 ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} 341 COMMAND 342 ${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS} 343 -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 344 -show-line-counts-or-regions ${EXCLUDE_REGEX} 345 DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 346 347 # Print out a summary of the coverage information to the command line 348 add_custom_target( 349 ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} 350 COMMAND 351 ${LLVM_COV_PATH} report $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS} 352 -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 353 ${EXCLUDE_REGEX} 354 DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 355 356 # Export coverage information so continuous integration tools (e.g. 357 # Jenkins) can consume it 358 add_custom_target( 359 ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} 360 COMMAND 361 ${LLVM_COV_PATH} export $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS} 362 -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 363 -format="text" ${EXCLUDE_REGEX} > 364 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json 365 DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 366 367 # Generates HTML output of the coverage information for perusal 368 add_custom_target( 369 ccov-${target_code_coverage_COVERAGE_TARGET_NAME} 370 COMMAND 371 ${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS} 372 -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata 373 -show-line-counts-or-regions 374 -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} 375 -format="html" ${EXCLUDE_REGEX} 376 DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) 377 378 elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 379 "GNU") 380 set(COVERAGE_INFO 381 "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" 382 ) 383 384 # Run the executable, generating coverage information 385 add_custom_target( 386 ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} 387 COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS} 388 DEPENDS ccov-preprocessing ${TARGET_NAME}) 389 390 # Generate exclusion string for use 391 foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) 392 set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} 393 '${EXCLUDE_ITEM}') 394 endforeach() 395 396 if(EXCLUDE_REGEX) 397 set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file 398 ${COVERAGE_INFO}) 399 else() 400 set(EXCLUDE_COMMAND ;) 401 endif() 402 403 if(NOT ${target_code_coverage_EXTERNAL}) 404 set(EXTERNAL_OPTION --no-external) 405 endif() 406 407 # Capture coverage data 408 if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 409 add_custom_target( 410 ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} 411 COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} 412 COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters 413 COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS} 414 COMMAND 415 ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory 416 ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file 417 ${COVERAGE_INFO} 418 COMMAND ${EXCLUDE_COMMAND} 419 DEPENDS ccov-preprocessing ${TARGET_NAME}) 420 else() 421 add_custom_target( 422 ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} 423 COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} 424 COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters 425 COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS} 426 COMMAND 427 ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory 428 ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file 429 ${COVERAGE_INFO} 430 COMMAND ${EXCLUDE_COMMAND} 431 DEPENDS ccov-preprocessing ${TARGET_NAME}) 432 endif() 433 434 # Generates HTML output of the coverage information for perusal 435 add_custom_target( 436 ccov-${target_code_coverage_COVERAGE_TARGET_NAME} 437 COMMAND 438 ${GENHTML_PATH} -o 439 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} 440 ${COVERAGE_INFO} 441 DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) 442 endif() 443 444 add_custom_command( 445 TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} 446 POST_BUILD 447 COMMAND ; 448 COMMENT 449 "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." 450 ) 451 452 # AUTO 453 if(target_code_coverage_AUTO) 454 if(NOT TARGET ccov) 455 add_custom_target(ccov) 456 endif() 457 add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) 458 459 if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID 460 MATCHES "GNU") 461 if(NOT TARGET ccov-report) 462 add_custom_target(ccov-report) 463 endif() 464 add_dependencies( 465 ccov-report 466 ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) 467 endif() 468 endif() 469 470 # ALL 471 if(target_code_coverage_ALL) 472 if(NOT TARGET ccov-all-processing) 473 message( 474 FATAL_ERROR 475 "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." 476 ) 477 endif() 478 479 add_dependencies(ccov-all-processing 480 ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) 481 endif() 482 endif() 483 endif() 484endfunction() 485 486# Adds code coverage instrumentation to all targets in the current directory and 487# any subdirectories. To add coverage instrumentation to only specific targets, 488# use `target_code_coverage`. 489function(add_code_coverage) 490 if(CODE_COVERAGE) 491 if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 492 OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 493 add_compile_options(-fprofile-instr-generate -fcoverage-mapping) 494 add_link_options(-fprofile-instr-generate -fcoverage-mapping) 495 elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 496 "GNU") 497 add_compile_options(-fprofile-arcs -ftest-coverage) 498 link_libraries(gcov) 499 endif() 500 endif() 501endfunction() 502 503# Adds the 'ccov-all' type targets that calls all targets added via 504# `target_code_coverage` with the `ALL` parameter, but merges all the coverage 505# data from them into a single large report instead of the numerous smaller 506# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for 507# use with coverage dashboards (e.g. codecov.io, coveralls). 508# ~~~ 509# Optional: 510# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! 511# ~~~ 512function(add_code_coverage_all_targets) 513 # Argument parsing 514 set(multi_value_keywords EXCLUDE) 515 cmake_parse_arguments(add_code_coverage_all_targets "" "" 516 "${multi_value_keywords}" ${ARGN}) 517 518 if(CODE_COVERAGE) 519 if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" 520 OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 521 522 # Merge the profile data for all of the run executables 523 if(WIN32) 524 add_custom_target( 525 ccov-all-processing 526 COMMAND 527 powershell -Command $$FILELIST = Get-Content 528 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe 529 merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 530 -sparse $$FILELIST) 531 else() 532 add_custom_target( 533 ccov-all-processing 534 COMMAND 535 ${LLVM_PROFDATA_PATH} merge -o 536 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat 537 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) 538 endif() 539 540 # Regex exclude only available for LLVM >= 7 541 if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") 542 foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) 543 set(EXCLUDE_REGEX ${EXCLUDE_REGEX} 544 -ignore-filename-regex='${EXCLUDE_ITEM}') 545 endforeach() 546 endif() 547 548 # Print summary of the code coverage information to the command line 549 if(WIN32) 550 add_custom_target( 551 ccov-all-report 552 COMMAND 553 powershell -Command $$FILELIST = Get-Content 554 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe 555 report $$FILELIST 556 -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 557 ${EXCLUDE_REGEX} 558 DEPENDS ccov-all-processing) 559 else() 560 add_custom_target( 561 ccov-all-report 562 COMMAND 563 ${LLVM_COV_PATH} report `cat 564 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` 565 -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 566 ${EXCLUDE_REGEX} 567 DEPENDS ccov-all-processing) 568 endif() 569 570 # Export coverage information so continuous integration tools (e.g. 571 # Jenkins) can consume it 572 add_custom_target( 573 ccov-all-export 574 COMMAND 575 ${LLVM_COV_PATH} export `cat 576 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` 577 -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 578 -format="text" ${EXCLUDE_REGEX} > 579 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json 580 DEPENDS ccov-all-processing) 581 582 # Generate HTML output of all added targets for perusal 583 if(WIN32) 584 add_custom_target( 585 ccov-all 586 COMMAND 587 powershell -Command $$FILELIST = Get-Content 588 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show 589 $$FILELIST 590 -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 591 -show-line-counts-or-regions 592 -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged 593 -format="html" ${EXCLUDE_REGEX} 594 DEPENDS ccov-all-processing) 595 else() 596 add_custom_target( 597 ccov-all 598 COMMAND 599 ${LLVM_COV_PATH} show `cat 600 ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` 601 -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata 602 -show-line-counts-or-regions 603 -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged 604 -format="html" ${EXCLUDE_REGEX} 605 DEPENDS ccov-all-processing) 606 endif() 607 608 elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 609 "GNU") 610 set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") 611 612 # Nothing required for gcov 613 add_custom_target(ccov-all-processing COMMAND ;) 614 615 # Exclusion regex string creation 616 set(EXCLUDE_REGEX) 617 foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) 618 set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} 619 '${EXCLUDE_ITEM}') 620 endforeach() 621 622 if(EXCLUDE_REGEX) 623 set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file 624 ${COVERAGE_INFO}) 625 else() 626 set(EXCLUDE_COMMAND ;) 627 endif() 628 629 # Capture coverage data 630 if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 631 add_custom_target( 632 ccov-all-capture 633 COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} 634 COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture 635 --output-file ${COVERAGE_INFO} 636 COMMAND ${EXCLUDE_COMMAND} 637 DEPENDS ccov-preprocessing ccov-all-processing) 638 else() 639 add_custom_target( 640 ccov-all-capture 641 COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} 642 COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture 643 --output-file ${COVERAGE_INFO} 644 COMMAND ${EXCLUDE_COMMAND} 645 DEPENDS ccov-preprocessing ccov-all-processing) 646 endif() 647 648 # Generates HTML output of all targets for perusal 649 add_custom_target( 650 ccov-all 651 COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged 652 ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} 653 DEPENDS ccov-all-capture) 654 655 endif() 656 657 add_custom_command( 658 TARGET ccov-all 659 POST_BUILD 660 COMMAND ; 661 COMMENT 662 "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." 663 ) 664 endif() 665endfunction() 666