CTestCoverageCollectGCOV.cmake 10 KB

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