• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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