CTestCoverageCollectGCOV.cmake 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. # file Copyright.txt or https://cmake.org/licensing for details.
  3. #[=======================================================================[.rst:
  4. CTestCoverageCollectGCOV
  5. ------------------------
  6. This module provides the ``ctest_coverage_collect_gcov`` function.
  7. This function runs gcov on all .gcda files found in the binary tree
  8. and packages the resulting .gcov files into a tar file.
  9. This tarball also contains the following:
  10. * *data.json* defines the source and build directories for use by CDash.
  11. * *Labels.json* indicates any :prop_sf:`LABELS` that have been set on the
  12. source files.
  13. * The *uncovered* directory holds any uncovered files found by
  14. :variable:`CTEST_EXTRA_COVERAGE_GLOB`.
  15. After generating this tar file, it can be sent to CDash for display with the
  16. :command:`ctest_submit(CDASH_UPLOAD)` command.
  17. .. command:: ctest_coverage_collect_gcov
  18. ::
  19. ctest_coverage_collect_gcov(TARBALL <tarfile>
  20. [SOURCE <source_dir>][BUILD <build_dir>]
  21. [GCOV_COMMAND <gcov_command>]
  22. [GCOV_OPTIONS <options>...]
  23. )
  24. Run gcov and package a tar file for CDash. The options are:
  25. ``TARBALL <tarfile>``
  26. Specify the location of the ``.tar`` file to be created for later
  27. upload to CDash. Relative paths will be interpreted with respect
  28. to the top-level build directory.
  29. ``SOURCE <source_dir>``
  30. Specify the top-level source directory for the build.
  31. Default is the value of :variable:`CTEST_SOURCE_DIRECTORY`.
  32. ``BUILD <build_dir>``
  33. Specify the top-level build directory for the build.
  34. Default is the value of :variable:`CTEST_BINARY_DIRECTORY`.
  35. ``GCOV_COMMAND <gcov_command>``
  36. Specify the full path to the ``gcov`` command on the machine.
  37. Default is the value of :variable:`CTEST_COVERAGE_COMMAND`.
  38. ``GCOV_OPTIONS <options>...``
  39. Specify options to be passed to gcov. The ``gcov`` command
  40. is run as ``gcov <options>... -o <gcov-dir> <file>.gcda``.
  41. If not specified, the default option is just ``-b -x``.
  42. ``GLOB``
  43. Recursively search for .gcda files in build_dir rather than
  44. determining search locations by reading TargetDirectories.txt.
  45. ``DELETE``
  46. Delete coverage files after they've been packaged into the .tar.
  47. ``QUIET``
  48. Suppress non-error messages that otherwise would have been
  49. printed out by this function.
  50. #]=======================================================================]
  51. function(ctest_coverage_collect_gcov)
  52. set(options QUIET GLOB DELETE)
  53. set(oneValueArgs TARBALL SOURCE BUILD GCOV_COMMAND)
  54. set(multiValueArgs GCOV_OPTIONS)
  55. cmake_parse_arguments(GCOV "${options}" "${oneValueArgs}"
  56. "${multiValueArgs}" "" ${ARGN} )
  57. if(NOT DEFINED GCOV_TARBALL)
  58. message(FATAL_ERROR
  59. "TARBALL must be specified. for ctest_coverage_collect_gcov")
  60. endif()
  61. if(NOT DEFINED GCOV_SOURCE)
  62. set(source_dir "${CTEST_SOURCE_DIRECTORY}")
  63. else()
  64. set(source_dir "${GCOV_SOURCE}")
  65. endif()
  66. if(NOT DEFINED GCOV_BUILD)
  67. set(binary_dir "${CTEST_BINARY_DIRECTORY}")
  68. else()
  69. set(binary_dir "${GCOV_BUILD}")
  70. endif()
  71. if(NOT DEFINED GCOV_GCOV_COMMAND)
  72. set(gcov_command "${CTEST_COVERAGE_COMMAND}")
  73. else()
  74. set(gcov_command "${GCOV_GCOV_COMMAND}")
  75. endif()
  76. # run gcov on each gcda file in the binary tree
  77. set(gcda_files)
  78. set(label_files)
  79. if (GCOV_GLOB)
  80. file(GLOB_RECURSE gfiles "${binary_dir}/*.gcda")
  81. list(LENGTH gfiles len)
  82. # if we have gcda files then also grab the labels file for that target
  83. if(${len} GREATER 0)
  84. file(GLOB_RECURSE lfiles RELATIVE ${binary_dir} "${binary_dir}/Labels.json")
  85. list(APPEND gcda_files ${gfiles})
  86. list(APPEND label_files ${lfiles})
  87. endif()
  88. else()
  89. # look for gcda files in the target directories
  90. # this will be faster and only look where the files will be
  91. file(STRINGS "${binary_dir}/CMakeFiles/TargetDirectories.txt" target_dirs
  92. ENCODING UTF-8)
  93. foreach(target_dir ${target_dirs})
  94. file(GLOB_RECURSE gfiles "${target_dir}/*.gcda")
  95. list(LENGTH gfiles len)
  96. # if we have gcda files then also grab the labels file for that target
  97. if(${len} GREATER 0)
  98. file(GLOB_RECURSE lfiles RELATIVE ${binary_dir}
  99. "${target_dir}/Labels.json")
  100. list(APPEND gcda_files ${gfiles})
  101. list(APPEND label_files ${lfiles})
  102. endif()
  103. endforeach()
  104. endif()
  105. # return early if no coverage files were found
  106. list(LENGTH gcda_files len)
  107. if(len EQUAL 0)
  108. if (NOT GCOV_QUIET)
  109. message("ctest_coverage_collect_gcov: No .gcda files found, "
  110. "ignoring coverage request.")
  111. endif()
  112. return()
  113. endif()
  114. # setup the dir for the coverage files
  115. set(coverage_dir "${binary_dir}/Testing/CoverageInfo")
  116. file(MAKE_DIRECTORY "${coverage_dir}")
  117. # run gcov, this will produce the .gcov files in the current
  118. # working directory
  119. if(NOT DEFINED GCOV_GCOV_OPTIONS)
  120. set(GCOV_GCOV_OPTIONS -b -x)
  121. endif()
  122. execute_process(COMMAND
  123. ${gcov_command} ${GCOV_GCOV_OPTIONS} ${gcda_files}
  124. OUTPUT_VARIABLE out
  125. RESULT_VARIABLE res
  126. WORKING_DIRECTORY ${coverage_dir})
  127. if (GCOV_DELETE)
  128. file(REMOVE ${gcda_files})
  129. endif()
  130. if(NOT "${res}" EQUAL 0)
  131. if (NOT GCOV_QUIET)
  132. message(STATUS "Error running gcov: ${res} ${out}")
  133. endif()
  134. endif()
  135. # create json file with project information
  136. file(WRITE ${coverage_dir}/data.json
  137. "{
  138. \"Source\": \"${source_dir}\",
  139. \"Binary\": \"${binary_dir}\"
  140. }")
  141. # collect the gcov files
  142. set(unfiltered_gcov_files)
  143. file(GLOB_RECURSE unfiltered_gcov_files RELATIVE ${binary_dir} "${coverage_dir}/*.gcov")
  144. # if CTEST_EXTRA_COVERAGE_GLOB was specified we search for files
  145. # that might be uncovered
  146. if (DEFINED CTEST_EXTRA_COVERAGE_GLOB)
  147. set(uncovered_files)
  148. foreach(search_entry IN LISTS CTEST_EXTRA_COVERAGE_GLOB)
  149. if(NOT GCOV_QUIET)
  150. message("Add coverage glob: ${search_entry}")
  151. endif()
  152. file(GLOB_RECURSE matching_files "${source_dir}/${search_entry}")
  153. if (matching_files)
  154. list(APPEND uncovered_files "${matching_files}")
  155. endif()
  156. endforeach()
  157. endif()
  158. set(gcov_files)
  159. foreach(gcov_file ${unfiltered_gcov_files})
  160. file(STRINGS ${binary_dir}/${gcov_file} first_line LIMIT_COUNT 1 ENCODING UTF-8)
  161. set(is_excluded false)
  162. if(first_line MATCHES "^ -: 0:Source:(.*)$")
  163. set(source_file ${CMAKE_MATCH_1})
  164. elseif(NOT GCOV_QUIET)
  165. message(STATUS "Could not determine source file corresponding to: ${gcov_file}")
  166. endif()
  167. foreach(exclude_entry IN LISTS CTEST_CUSTOM_COVERAGE_EXCLUDE)
  168. if(source_file MATCHES "${exclude_entry}")
  169. set(is_excluded true)
  170. if(NOT GCOV_QUIET)
  171. message("Excluding coverage for: ${source_file} which matches ${exclude_entry}")
  172. endif()
  173. break()
  174. endif()
  175. endforeach()
  176. get_filename_component(resolved_source_file "${source_file}" ABSOLUTE)
  177. foreach(uncovered_file IN LISTS uncovered_files)
  178. get_filename_component(resolved_uncovered_file "${uncovered_file}" ABSOLUTE)
  179. if (resolved_uncovered_file STREQUAL resolved_source_file)
  180. list(REMOVE_ITEM uncovered_files "${uncovered_file}")
  181. endif()
  182. endforeach()
  183. if(NOT is_excluded)
  184. list(APPEND gcov_files ${gcov_file})
  185. endif()
  186. endforeach()
  187. foreach (uncovered_file ${uncovered_files})
  188. # Check if this uncovered file should be excluded.
  189. set(is_excluded false)
  190. foreach(exclude_entry IN LISTS CTEST_CUSTOM_COVERAGE_EXCLUDE)
  191. if(uncovered_file MATCHES "${exclude_entry}")
  192. set(is_excluded true)
  193. if(NOT GCOV_QUIET)
  194. message("Excluding coverage for: ${uncovered_file} which matches ${exclude_entry}")
  195. endif()
  196. break()
  197. endif()
  198. endforeach()
  199. if(is_excluded)
  200. continue()
  201. endif()
  202. # Copy from source to binary dir, preserving any intermediate subdirectories.
  203. get_filename_component(filename "${uncovered_file}" NAME)
  204. get_filename_component(relative_path "${uncovered_file}" DIRECTORY)
  205. string(REPLACE "${source_dir}" "" relative_path "${relative_path}")
  206. if (relative_path)
  207. # Strip leading slash.
  208. string(SUBSTRING "${relative_path}" 1 -1 relative_path)
  209. endif()
  210. file(COPY ${uncovered_file} DESTINATION ${binary_dir}/uncovered/${relative_path})
  211. if(relative_path)
  212. list(APPEND uncovered_files_for_tar uncovered/${relative_path}/${filename})
  213. else()
  214. list(APPEND uncovered_files_for_tar uncovered/${filename})
  215. endif()
  216. endforeach()
  217. # tar up the coverage info with the same date so that the md5
  218. # sum will be the same for the tar file independent of file time
  219. # stamps
  220. string(REPLACE ";" "\n" gcov_files "${gcov_files}")
  221. string(REPLACE ";" "\n" label_files "${label_files}")
  222. string(REPLACE ";" "\n" uncovered_files_for_tar "${uncovered_files_for_tar}")
  223. file(WRITE "${coverage_dir}/coverage_file_list.txt"
  224. "${gcov_files}
  225. ${coverage_dir}/data.json
  226. ${label_files}
  227. ${uncovered_files_for_tar}
  228. ")
  229. if (GCOV_QUIET)
  230. set(tar_opts "cfj")
  231. else()
  232. set(tar_opts "cvfj")
  233. endif()
  234. execute_process(COMMAND
  235. ${CMAKE_COMMAND} -E tar ${tar_opts} ${GCOV_TARBALL}
  236. "--mtime=1970-01-01 0:0:0 UTC"
  237. "--format=gnutar"
  238. --files-from=${coverage_dir}/coverage_file_list.txt
  239. WORKING_DIRECTORY ${binary_dir})
  240. if (GCOV_DELETE)
  241. foreach(gcov_file ${unfiltered_gcov_files})
  242. file(REMOVE ${binary_dir}/${gcov_file})
  243. endforeach()
  244. file(REMOVE ${coverage_dir}/coverage_file_list.txt)
  245. file(REMOVE ${coverage_dir}/data.json)
  246. if (EXISTS ${binary_dir}/uncovered)
  247. file(REMOVE ${binary_dir}/uncovered)
  248. endif()
  249. endif()
  250. endfunction()