CMake/Modules/CTestCoverageCollectGCOV.cmake
Zack Galbreath 062045b8b6 More options for CTestCoverageCollectGCOV
This commit introduces two new options to CTestCoverageCollectGCOV.

When GLOB is set we recursively search in the source & binary
directories for .gcda files.  Otherwise the default behavior is to
parse TargetDirectories.txt for a list of locations to search.

When DELETE is set we remove any .gcda file found after it has
been used to generate the corresponding .gcov file.  The .gcov
file is also removed after the result tarball has been created.

Together these two new features help support the use case of
computing coverage across subprojects.
2016-02-10 10:12:01 -05:00

235 lines
7.9 KiB
CMake

#.rst:
# CTestCoverageCollectGCOV
# ------------------------
#
# This module provides the function ``ctest_coverage_collect_gcov``.
# The function will run gcov on the .gcda files in a binary tree and then
# package all of the .gcov files into a tar file with a data.json that
# contains the source and build directories for CDash to use in parsing
# the coverage data. In addtion the Labels.json files for targets that
# have coverage information are also put in the tar file for CDash to
# asign the correct labels. This file can be sent to a CDash server for
# display with the
# :command:`ctest_submit(CDASH_UPLOAD)` command.
#
# .. command:: cdash_coverage_collect_gcov
#
# ::
#
# ctest_coverage_collect_gcov(TARBALL <tarfile>
# [SOURCE <source_dir>][BUILD <build_dir>]
# [GCOV_COMMAND <gcov_command>]
# [GCOV_OPTIONS <options>...]
# )
#
# Run gcov and package a tar file for CDash. The options are:
#
# ``TARBALL <tarfile>``
# Specify the location of the ``.tar`` file to be created for later
# upload to CDash. Relative paths will be interpreted with respect
# to the top-level build directory.
#
# ``SOURCE <source_dir>``
# Specify the top-level source directory for the build.
# Default is the value of :variable:`CTEST_SOURCE_DIRECTORY`.
#
# ``BUILD <build_dir>``
# Specify the top-level build directory for the build.
# Default is the value of :variable:`CTEST_BINARY_DIRECTORY`.
#
# ``GCOV_COMMAND <gcov_command>``
# Specify the full path to the ``gcov`` command on the machine.
# Default is the value of :variable:`CTEST_COVERAGE_COMMAND`.
#
# ``GCOV_OPTIONS <options>...``
# Specify options to be passed to gcov. The ``gcov`` command
# is run as ``gcov <options>... -o <gcov-dir> <file>.gcda``.
# If not specified, the default option is just ``-b``.
#
# ``GLOB``
# Recursively search for .gcda files in build_dir rather than
# determining search locations by reading TargetDirectories.txt.
#
# ``DELETE``
# Delete coverage files after they've been packaged into the .tar.
#
# ``QUIET``
# Suppress non-error messages that otherwise would have been
# printed out by this function.
#=============================================================================
# Copyright 2014-2015 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
include(CMakeParseArguments)
function(ctest_coverage_collect_gcov)
set(options QUIET GLOB DELETE)
set(oneValueArgs TARBALL SOURCE BUILD GCOV_COMMAND)
set(multiValueArgs GCOV_OPTIONS)
cmake_parse_arguments(GCOV "${options}" "${oneValueArgs}"
"${multiValueArgs}" "" ${ARGN} )
if(NOT DEFINED GCOV_TARBALL)
message(FATAL_ERROR
"TARBALL must be specified. for ctest_coverage_collect_gcov")
endif()
if(NOT DEFINED GCOV_SOURCE)
set(source_dir "${CTEST_SOURCE_DIRECTORY}")
else()
set(source_dir "${GCOV_SOURCE}")
endif()
if(NOT DEFINED GCOV_BUILD)
set(binary_dir "${CTEST_BINARY_DIRECTORY}")
else()
set(binary_dir "${GCOV_BUILD}")
endif()
if(NOT DEFINED GCOV_GCOV_COMMAND)
set(gcov_command "${CTEST_COVERAGE_COMMAND}")
else()
set(gcov_command "${GCOV_GCOV_COMMAND}")
endif()
# run gcov on each gcda file in the binary tree
set(gcda_files)
set(label_files)
if (GCOV_GLOB)
file(GLOB_RECURSE gfiles RELATIVE ${binary_dir} "*.gcda")
list(LENGTH gfiles len)
# if we have gcda files then also grab the labels file for that target
if(${len} GREATER 0)
file(GLOB_RECURSE lfiles RELATIVE ${binary_dir} "Labels.json")
list(APPEND gcda_files ${gfiles})
list(APPEND label_files ${lfiles})
endif()
else()
# look for gcda files in the target directories
# this will be faster and only look where the files will be
file(STRINGS "${binary_dir}/CMakeFiles/TargetDirectories.txt" target_dirs
ENCODING UTF-8)
foreach(target_dir ${target_dirs})
file(GLOB_RECURSE gfiles RELATIVE ${binary_dir} "${target_dir}/*.gcda")
list(LENGTH gfiles len)
# if we have gcda files then also grab the labels file for that target
if(${len} GREATER 0)
file(GLOB_RECURSE lfiles RELATIVE ${binary_dir}
"${target_dir}/Labels.json")
list(APPEND gcda_files ${gfiles})
list(APPEND label_files ${lfiles})
endif()
endforeach()
endif()
# return early if no coverage files were found
list(LENGTH gcda_files len)
if(len EQUAL 0)
if (NOT GCOV_QUIET)
message("ctest_coverage_collect_gcov: No .gcda files found, "
"ignoring coverage request.")
endif()
return()
endif()
# setup the dir for the coverage files
set(coverage_dir "${binary_dir}/Testing/CoverageInfo")
file(MAKE_DIRECTORY "${coverage_dir}")
# call gcov on each .gcda file
foreach (gcda_file ${gcda_files})
# get the directory of the gcda file
get_filename_component(gcda_file ${binary_dir}/${gcda_file} ABSOLUTE)
get_filename_component(gcov_dir ${gcda_file} DIRECTORY)
# run gcov, this will produce the .gcov file in the current
# working directory
if(NOT DEFINED GCOV_GCOV_OPTIONS)
set(GCOV_GCOV_OPTIONS -b)
endif()
execute_process(COMMAND
${gcov_command} ${GCOV_GCOV_OPTIONS} -o ${gcov_dir} ${gcda_file}
OUTPUT_VARIABLE out
RESULT_VARIABLE res
WORKING_DIRECTORY ${coverage_dir})
if (GCOV_DELETE)
file(REMOVE ${gcda_file})
endif()
endforeach()
if(NOT "${res}" EQUAL 0)
if (NOT GCOV_QUIET)
message(STATUS "Error running gcov: ${res} ${out}")
endif()
endif()
# create json file with project information
file(WRITE ${coverage_dir}/data.json
"{
\"Source\": \"${source_dir}\",
\"Binary\": \"${binary_dir}\"
}")
# collect the gcov files
set(unfiltered_gcov_files)
file(GLOB_RECURSE unfiltered_gcov_files RELATIVE ${binary_dir} "${coverage_dir}/*.gcov")
set(gcov_files)
foreach(gcov_file ${unfiltered_gcov_files})
file(STRINGS ${binary_dir}/${gcov_file} first_line LIMIT_COUNT 1 ENCODING UTF-8)
set(is_excluded false)
if(first_line MATCHES "^ -: 0:Source:(.*)$")
set(source_file ${CMAKE_MATCH_1})
elseif(NOT GCOV_QUIET)
message(STATUS "Could not determine source file corresponding to: ${gcov_file}")
endif()
foreach(exclude_entry IN LISTS CTEST_CUSTOM_COVERAGE_EXCLUDE)
if(source_file MATCHES "${exclude_entry}")
set(is_excluded true)
if(NOT GCOV_QUIET)
message("Excluding coverage for: ${source_file} which matches ${exclude_entry}")
endif()
break()
endif()
endforeach()
if(NOT is_excluded)
list(APPEND gcov_files ${gcov_file})
endif()
endforeach()
# tar up the coverage info with the same date so that the md5
# sum will be the same for the tar file independent of file time
# stamps
string(REPLACE ";" "\n" gcov_files "${gcov_files}")
string(REPLACE ";" "\n" label_files "${label_files}")
file(WRITE "${coverage_dir}/coverage_file_list.txt"
"${gcov_files}
${coverage_dir}/data.json
${label_files}
")
if (GCOV_QUIET)
set(tar_opts "cfj")
else()
set(tar_opts "cvfj")
endif()
execute_process(COMMAND
${CMAKE_COMMAND} -E tar ${tar_opts} ${GCOV_TARBALL}
"--mtime=1970-01-01 0:0:0 UTC"
"--format=gnutar"
--files-from=${coverage_dir}/coverage_file_list.txt
WORKING_DIRECTORY ${binary_dir})
if (GCOV_DELETE)
string(REPLACE "\n" ";" gcov_files "${gcov_files}")
foreach(gcov_file ${gcov_files})
file(REMOVE ${binary_dir}/${gcov_file})
endforeach()
endif()
endfunction()