Add support for the lcov code coverage tool
authorCharles Giessen <charles@lunarg.com>
Thu, 9 Mar 2023 21:04:08 +0000 (14:04 -0700)
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>
Wed, 3 Apr 2024 20:38:56 +0000 (15:38 -0500)
CMakeLists.txt
loader/CMakeLists.txt
scripts/CodeCoverage.cmake [new file with mode: 0644]
tests/CMakeLists.txt

index 41078a9fbaeae4c2b83f981437eb4598b10844fd..7306ae2025fe59399888c02416de1987323239be 100644 (file)
@@ -20,6 +20,13 @@ cmake_minimum_required(VERSION 3.17.2)
 
 project(VULKAN_LOADER VERSION 1.3.281 LANGUAGES C)
 
+option(CODE_COVERAGE "Enable Code Coverage" OFF)
+if (CODE_COVERAGE)
+    include(scripts/CodeCoverage.cmake)
+    add_code_coverage_all_targets()
+endif()
+
+
 # This variable enables downstream users to customize the target API
 # variant (e.g. Vulkan SC)
 set(API_TYPE "vulkan")
index cae0a337289026fe040b09fe7992e7249acd1a03..3cef9b6c32f4fa615705afe968075ff2929ed950 100644 (file)
@@ -533,3 +533,7 @@ if (PKG_CONFIG_FOUND)
     configure_file("vulkan.pc.in" "vulkan.pc" @ONLY)
     install(FILES "${CMAKE_CURRENT_BINARY_DIR}/vulkan.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" RENAME "${API_TYPE}.pc")
 endif()
+
+if (CODE_COVERAGE)
+    target_code_coverage(vulkan AUTO ALL)
+endif()
diff --git a/scripts/CodeCoverage.cmake b/scripts/CodeCoverage.cmake
new file mode 100644 (file)
index 0000000..4b9ca0e
--- /dev/null
@@ -0,0 +1,665 @@
+#
+# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# USAGE: To enable any code coverage instrumentation/targets, the single CMake
+# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or
+# on the command line.
+#
+# From this point, there are two primary methods for adding instrumentation to
+# targets: 1 - A blanket instrumentation by calling `add_code_coverage()`, where
+# all targets in that directory and all subdirectories are automatically
+# instrumented. 2 - Per-target instrumentation by calling
+# `target_code_coverage(<TARGET_NAME>)`, where the target is given and thus only
+# that target is instrumented. This applies to both libraries and executables.
+#
+# To add coverage targets, such as calling `make ccov` to generate the actual
+# coverage information for perusal or consumption, call
+# `target_code_coverage(<TARGET_NAME>)` on an *executable* target.
+#
+# Example 1: All targets instrumented
+#
+# In this case, the coverage information reported will will be that of the
+# `theLib` library target and `theExe` executable.
+#
+# 1a: Via global command
+#
+# ~~~
+# add_code_coverage() # Adds instrumentation to all targets
+#
+# add_library(theLib lib.cpp)
+#
+# add_executable(theExe main.cpp)
+# target_link_libraries(theExe PRIVATE theLib)
+# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports.
+# ~~~
+#
+# 1b: Via target commands
+#
+# ~~~
+# add_library(theLib lib.cpp)
+# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets.
+#
+# add_executable(theExe main.cpp)
+# target_link_libraries(theExe PRIVATE theLib)
+# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports.
+# ~~~
+#
+# Example 2: Target instrumented, but with regex pattern of files to be excluded
+# from report
+#
+# ~~~
+# add_executable(theExe main.cpp non_covered.cpp)
+# 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.
+# ~~~
+#
+# Example 3: Target added to the 'ccov' and 'ccov-all' targets
+#
+# ~~~
+# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders.
+#
+# add_executable(theExe main.cpp non_covered.cpp)
+# 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.
+# ~~~
+
+# Options
+# option(
+#   CODE_COVERAGE
+#   "Builds targets with code coverage instrumentation. (Requires GCC or Clang)"
+#   OFF)
+
+# Programs
+find_program(LLVM_COV_PATH llvm-cov)
+find_program(LLVM_PROFDATA_PATH llvm-profdata)
+find_program(LCOV_PATH lcov)
+find_program(GENHTML_PATH genhtml)
+# Hide behind the 'advanced' mode flag for GUI/ccmake
+mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH)
+
+# Variables
+set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov)
+set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1)
+
+# Common initialization/checks
+if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED)
+  set(CODE_COVERAGE_ADDED ON)
+
+  # Common Targets
+  add_custom_target(
+    ccov-preprocessing
+    COMMAND ${CMAKE_COMMAND} -E make_directory
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}
+    DEPENDS ccov-clean)
+
+  if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
+     OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
+    # Messages
+    message(STATUS "Building with llvm Code Coverage Tools")
+
+    if(NOT LLVM_COV_PATH)
+      message(FATAL_ERROR "llvm-cov not found! Aborting.")
+    else()
+      # Version number checking for 'EXCLUDE' compatibility
+      execute_process(COMMAND ${LLVM_COV_PATH} --version
+                      OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT)
+      string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION
+                   ${LLVM_COV_VERSION_CALL_OUTPUT})
+
+      if(LLVM_COV_VERSION VERSION_LESS "7.0.0")
+        message(
+          WARNING
+            "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0"
+        )
+      endif()
+    endif()
+
+    # Targets
+    if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
+      add_custom_target(
+        ccov-clean
+        COMMAND ${CMAKE_COMMAND} -E remove -f
+                ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
+        COMMAND ${CMAKE_COMMAND} -E remove -f
+                ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
+    else()
+      add_custom_target(
+        ccov-clean
+        COMMAND ${CMAKE_COMMAND} -E rm -f
+                ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
+        COMMAND ${CMAKE_COMMAND} -E rm -f
+                ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
+    endif()
+
+    # Used to get the shared object file list before doing the main all-
+    # processing
+    add_custom_target(
+      ccov-libs
+      COMMAND ;
+      COMMENT "libs ready for coverage report.")
+
+  elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
+                                              "GNU")
+    # Messages
+    message(STATUS "Building with lcov Code Coverage Tools")
+
+    if(CMAKE_BUILD_TYPE)
+      string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type)
+      if(NOT ${upper_build_type} STREQUAL "DEBUG")
+        message(
+          WARNING
+            "Code coverage results with an optimized (non-Debug) build may be misleading"
+        )
+      endif()
+    else()
+      message(
+        WARNING
+          "Code coverage results with an optimized (non-Debug) build may be misleading"
+      )
+    endif()
+    if(NOT LCOV_PATH)
+      message(FATAL_ERROR "lcov not found! Aborting...")
+    endif()
+    if(NOT GENHTML_PATH)
+      message(FATAL_ERROR "genhtml not found! Aborting...")
+    endif()
+
+    # Targets
+    add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory
+                                         ${CMAKE_BINARY_DIR} --zerocounters)
+
+  else()
+    message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.")
+  endif()
+endif()
+
+# Adds code coverage instrumentation to a library, or instrumentation/targets
+# for an executable target.
+# ~~~
+# EXECUTABLE ADDED TARGETS:
+# GCOV/LCOV:
+# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
+# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target.
+# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
+#
+# LLVM-COV:
+# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
+# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter.
+# ccov-${TARGET_NAME} : Generates HTML code coverage report.
+# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information.
+# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file.
+# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information.
+# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
+# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line.
+# ccov-all-export : Exports the coverage report to a JSON file.
+#
+# Required:
+# TARGET_NAME - Name of the target to generate code coverage for.
+# Optional:
+# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE.
+# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE.
+# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets.
+# 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.
+# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory
+# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`.
+# 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.**
+# OBJECTS <TARGETS> - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output
+# ARGS <ARGUMENTS> - For executables ONLY, appends the given arguments to the associated ccov-* executable call
+# ~~~
+function(target_code_coverage TARGET_NAME)
+  # Argument parsing
+  set(options AUTO ALL EXTERNAL PUBLIC INTERFACE)
+  set(single_value_keywords COVERAGE_TARGET_NAME)
+  set(multi_value_keywords EXCLUDE OBJECTS ARGS)
+  cmake_parse_arguments(
+    target_code_coverage "${options}" "${single_value_keywords}"
+    "${multi_value_keywords}" ${ARGN})
+
+  # Set the visibility of target functions to PUBLIC, INTERFACE or default to
+  # PRIVATE.
+  if(target_code_coverage_PUBLIC)
+    set(TARGET_VISIBILITY PUBLIC)
+  elseif(target_code_coverage_INTERFACE)
+    set(TARGET_VISIBILITY INTERFACE)
+  else()
+    set(TARGET_VISIBILITY PRIVATE)
+  endif()
+
+  if(NOT target_code_coverage_COVERAGE_TARGET_NAME)
+    # If a specific name was given, use that instead.
+    set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME})
+  endif()
+
+  if(CODE_COVERAGE)
+
+    # Add code coverage instrumentation to the target's linker command
+    if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
+       OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
+      target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY}
+                             -fprofile-instr-generate -fcoverage-mapping)
+      target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY}
+                          -fprofile-instr-generate -fcoverage-mapping)
+    elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
+                                                "GNU")
+      target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs
+                             -ftest-coverage)
+      target_link_libraries(${TARGET_NAME} ${TARGET_VISIBILITY} gcov)
+    endif()
+
+    # Targets
+    get_target_property(target_type ${TARGET_NAME} TYPE)
+
+    # Add shared library to processing for 'all' targets
+    if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL)
+      if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
+         OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
+        add_custom_target(
+          ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>" >>
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
+          DEPENDS ccov-preprocessing ${TARGET_NAME})
+
+        if(NOT TARGET ccov-libs)
+          message(
+            FATAL_ERROR
+              "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'."
+          )
+        endif()
+
+        add_dependencies(ccov-libs
+                         ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
+      endif()
+    endif()
+
+    # For executables add targets to run and produce output
+    if(target_type STREQUAL "EXECUTABLE")
+      if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
+         OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
+
+        # If there are shared objects to also work with, generate the string to
+        # add them here
+        foreach(SO_TARGET ${target_code_coverage_OBJECTS})
+          # Check to see if the target is a shared object
+          if(TARGET ${SO_TARGET})
+            get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE)
+            if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY")
+              set(SO_OBJECTS ${SO_OBJECTS} -object=$<TARGET_FILE:${SO_TARGET}>)
+            endif()
+          endif()
+        endforeach()
+
+        # Run the executable, generating raw profile data Make the run data
+        # available for further processing. Separated to allow Windows to run
+        # this target serially.
+        add_custom_target(
+          ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${CMAKE_COMMAND} -E env
+            LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw
+            $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
+          COMMAND
+            ${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>"
+            ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
+          COMMAND
+            ${CMAKE_COMMAND} -E echo
+            "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw"
+            >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list
+          JOB_POOL ccov_serial_pool
+          DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME})
+
+        # Merge the generated profile data so llvm-cov can process it
+        add_custom_target(
+          ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${LLVM_PROFDATA_PATH} merge -sparse
+            ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o
+            ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
+          DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
+
+        # Ignore regex only works on LLVM >= 7
+        if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
+          foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
+            set(EXCLUDE_REGEX ${EXCLUDE_REGEX}
+                              -ignore-filename-regex='${EXCLUDE_ITEM}')
+          endforeach()
+        endif()
+
+        # Print out details of the coverage information to the command line
+        add_custom_target(
+          ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
+            -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
+            -show-line-counts-or-regions ${EXCLUDE_REGEX}
+          DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
+
+        # Print out a summary of the coverage information to the command line
+        add_custom_target(
+          ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${LLVM_COV_PATH} report $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
+            -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
+            ${EXCLUDE_REGEX}
+          DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
+
+        # Export coverage information so continuous integration tools (e.g.
+        # Jenkins) can consume it
+        add_custom_target(
+          ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${LLVM_COV_PATH} export $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
+            -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
+            -format="text" ${EXCLUDE_REGEX} >
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json
+          DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
+
+        # Generates HTML output of the coverage information for perusal
+        add_custom_target(
+          ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
+            -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
+            -show-line-counts-or-regions
+            -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
+            -format="html" ${EXCLUDE_REGEX}
+          DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
+
+      elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
+                                                  "GNU")
+        set(COVERAGE_INFO
+            "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info"
+        )
+
+        # Run the executable, generating coverage information
+        add_custom_target(
+          ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
+          DEPENDS ccov-preprocessing ${TARGET_NAME})
+
+        # Generate exclusion string for use
+        foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
+          set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
+                            '${EXCLUDE_ITEM}')
+        endforeach()
+
+        if(EXCLUDE_REGEX)
+          set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file
+                              ${COVERAGE_INFO})
+        else()
+          set(EXCLUDE_COMMAND ;)
+        endif()
+
+        if(NOT ${target_code_coverage_EXTERNAL})
+          set(EXTERNAL_OPTION --no-external)
+        endif()
+
+        # Capture coverage data
+        if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
+          add_custom_target(
+            ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
+            COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
+            COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
+            COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
+            COMMAND
+              ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
+              ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
+              ${COVERAGE_INFO}
+            COMMAND ${EXCLUDE_COMMAND}
+            DEPENDS ccov-preprocessing ${TARGET_NAME})
+        else()
+          add_custom_target(
+            ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
+            COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
+            COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
+            COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
+            COMMAND
+              ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
+              ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
+              ${COVERAGE_INFO}
+            COMMAND ${EXCLUDE_COMMAND}
+            DEPENDS ccov-preprocessing ${TARGET_NAME})
+        endif()
+
+        # Generates HTML output of the coverage information for perusal
+        add_custom_target(
+          ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
+          COMMAND
+            ${GENHTML_PATH} -o
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
+            ${COVERAGE_INFO}
+          DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME})
+      endif()
+
+      add_custom_command(
+        TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
+        POST_BUILD
+        COMMAND ;
+        COMMENT
+          "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report."
+      )
+
+      # AUTO
+      if(target_code_coverage_AUTO)
+        if(NOT TARGET ccov)
+          add_custom_target(ccov)
+        endif()
+        add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME})
+
+        if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID
+                                                     MATCHES "GNU")
+          if(NOT TARGET ccov-report)
+            add_custom_target(ccov-report)
+          endif()
+          add_dependencies(
+            ccov-report
+            ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME})
+        endif()
+      endif()
+
+      # ALL
+      if(target_code_coverage_ALL)
+        if(NOT TARGET ccov-all-processing)
+          message(
+            FATAL_ERROR
+              "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'."
+          )
+        endif()
+
+        add_dependencies(ccov-all-processing
+                         ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
+      endif()
+    endif()
+  endif()
+endfunction()
+
+# Adds code coverage instrumentation to all targets in the current directory and
+# any subdirectories. To add coverage instrumentation to only specific targets,
+# use `target_code_coverage`.
+function(add_code_coverage)
+  if(CODE_COVERAGE)
+    if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
+       OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
+      add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
+      add_link_options(-fprofile-instr-generate -fcoverage-mapping)
+    elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
+                                              "GNU")
+      add_compile_options(-fprofile-arcs -ftest-coverage)
+      link_libraries(gcov)
+    endif()
+  endif()
+endfunction()
+
+# Adds the 'ccov-all' type targets that calls all targets added via
+# `target_code_coverage` with the `ALL` parameter, but merges all the coverage
+# data from them into a single large report  instead of the numerous smaller
+# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for
+# use with coverage dashboards (e.g. codecov.io, coveralls).
+# ~~~
+# Optional:
+# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex!
+# ~~~
+function(add_code_coverage_all_targets)
+  # Argument parsing
+  set(multi_value_keywords EXCLUDE)
+  cmake_parse_arguments(add_code_coverage_all_targets "" ""
+                        "${multi_value_keywords}" ${ARGN})
+
+  if(CODE_COVERAGE)
+    if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
+       OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
+
+      # Merge the profile data for all of the run executables
+      if(WIN32)
+        add_custom_target(
+          ccov-all-processing
+          COMMAND
+            powershell -Command $$FILELIST = Get-Content
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe
+            merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
+            -sparse $$FILELIST)
+      else()
+        add_custom_target(
+          ccov-all-processing
+          COMMAND
+            ${LLVM_PROFDATA_PATH} merge -o
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`)
+      endif()
+
+      # Regex exclude only available for LLVM >= 7
+      if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
+        foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
+          set(EXCLUDE_REGEX ${EXCLUDE_REGEX}
+                            -ignore-filename-regex='${EXCLUDE_ITEM}')
+        endforeach()
+      endif()
+
+      # Print summary of the code coverage information to the command line
+      if(WIN32)
+        add_custom_target(
+          ccov-all-report
+          COMMAND
+            powershell -Command $$FILELIST = Get-Content
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe
+            report $$FILELIST
+            -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
+            ${EXCLUDE_REGEX}
+          DEPENDS ccov-all-processing)
+      else()
+        add_custom_target(
+          ccov-all-report
+          COMMAND
+            ${LLVM_COV_PATH} report `cat
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
+            -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
+            ${EXCLUDE_REGEX}
+          DEPENDS ccov-all-processing)
+      endif()
+
+      # Export coverage information so continuous integration tools (e.g.
+      # Jenkins) can consume it
+      add_custom_target(
+        ccov-all-export
+        COMMAND
+          ${LLVM_COV_PATH} export `cat
+          ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
+          -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
+          -format="text" ${EXCLUDE_REGEX} >
+          ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json
+        DEPENDS ccov-all-processing)
+
+      # Generate HTML output of all added targets for perusal
+      if(WIN32)
+        add_custom_target(
+          ccov-all
+          COMMAND
+            powershell -Command $$FILELIST = Get-Content
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show
+            $$FILELIST
+            -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
+            -show-line-counts-or-regions
+            -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
+            -format="html" ${EXCLUDE_REGEX}
+          DEPENDS ccov-all-processing)
+      else()
+        add_custom_target(
+          ccov-all
+          COMMAND
+            ${LLVM_COV_PATH} show `cat
+            ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
+            -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
+            -show-line-counts-or-regions
+            -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
+            -format="html" ${EXCLUDE_REGEX}
+          DEPENDS ccov-all-processing)
+      endif()
+
+    elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
+                                                "GNU")
+      set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info")
+
+      # Nothing required for gcov
+      add_custom_target(ccov-all-processing COMMAND ;)
+
+      # Exclusion regex string creation
+      set(EXCLUDE_REGEX)
+      foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
+        set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
+                          '${EXCLUDE_ITEM}')
+      endforeach()
+
+      if(EXCLUDE_REGEX)
+        set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file
+                            ${COVERAGE_INFO})
+      else()
+        set(EXCLUDE_COMMAND ;)
+      endif()
+
+      # Capture coverage data
+      if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
+        add_custom_target(
+          ccov-all-capture
+          COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
+          COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
+                  --output-file ${COVERAGE_INFO}
+          COMMAND ${EXCLUDE_COMMAND}
+          DEPENDS ccov-preprocessing ccov-all-processing)
+      else()
+        add_custom_target(
+          ccov-all-capture
+          COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
+          COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
+                  --output-file ${COVERAGE_INFO}
+          COMMAND ${EXCLUDE_COMMAND}
+          DEPENDS ccov-preprocessing ccov-all-processing)
+      endif()
+
+      # Generates HTML output of all targets for perusal
+      add_custom_target(
+        ccov-all
+        COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
+                ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR}
+        DEPENDS ccov-all-capture)
+
+    endif()
+
+    add_custom_command(
+      TARGET ccov-all
+      POST_BUILD
+      COMMAND ;
+      COMMENT
+        "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report."
+    )
+  endif()
+endfunction()
index 10273899b79e039284bf1de37f2bb7a4d3210373..fbc4fc3f2273db93edafb74f344614d4bac3aa9e 100644 (file)
@@ -169,3 +169,7 @@ add_test(NAME integration.find_package
 
 # Installing comes before testing
 set_tests_properties(integration.find_package PROPERTIES DEPENDS integration.install)
+
+if (CODE_COVERAGE)
+    target_code_coverage(test_regression AUTO ALL )
+endif()