Merge topic 'cpack-innosetup-linux'
[kiteware-cmake.git] / Modules / CTestCoverageCollectGCOV.cmake
bloba6fa3a4c6cdc2599e81154407665dd3545686a2c
1 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2 # file Copyright.txt or https://cmake.org/licensing for details.
4 #[=======================================================================[.rst:
5 CTestCoverageCollectGCOV
6 ------------------------
8 .. versionadded:: 3.2
10 This module provides the ``ctest_coverage_collect_gcov`` function.
12 This function runs gcov on all .gcda files found in the binary tree
13 and packages the resulting .gcov files into a tar file.
14 This tarball also contains the following:
16 * *data.json* defines the source and build directories for use by CDash.
17 * *Labels.json* indicates any :prop_sf:`LABELS` that have been set on the
18   source files.
19 * The *uncovered* directory holds any uncovered files found by
20   :variable:`CTEST_EXTRA_COVERAGE_GLOB`.
22 After generating this tar file, it can be sent to CDash for display with the
23 :command:`ctest_submit(CDASH_UPLOAD)` command.
25 .. command:: ctest_coverage_collect_gcov
27   ::
29     ctest_coverage_collect_gcov(TARBALL <tarfile>
30       [SOURCE <source_dir>][BUILD <build_dir>]
31       [GCOV_COMMAND <gcov_command>]
32       [GCOV_OPTIONS <options>...]
33       )
35   Run gcov and package a tar file for CDash.  The options are:
37   ``TARBALL <tarfile>``
38     Specify the location of the ``.tar`` file to be created for later
39     upload to CDash.  Relative paths will be interpreted with respect
40     to the top-level build directory.
42   ``TARBALL_COMPRESSION <option>``
43     .. versionadded:: 3.18
45     Specify a compression algorithm for the
46     ``TARBALL`` data file.  Using this option reduces the size of the data file
47     before it is submitted to CDash.  ``<option>`` must be one of ``GZIP``,
48     ``BZIP2``, ``XZ``, ``ZSTD``, ``FROM_EXT``, or an expression that CMake
49     evaluates as ``FALSE``. The default value is ``BZIP2``.
51     If ``FROM_EXT`` is specified, the resulting file will be compressed based on
52     the file extension of the ``<tarfile>`` (i.e. ``.tar.gz`` will use ``GZIP``
53     compression). File extensions that will produce compressed output include
54     ``.tar.gz``, ``.tgz``, ``.tar.bzip2``, ``.tbz``, ``.tar.xz``, and ``.txz``.
56   ``SOURCE <source_dir>``
57     Specify the top-level source directory for the build.
58     Default is the value of :variable:`CTEST_SOURCE_DIRECTORY`.
60   ``BUILD <build_dir>``
61     Specify the top-level build directory for the build.
62     Default is the value of :variable:`CTEST_BINARY_DIRECTORY`.
64   ``GCOV_COMMAND <gcov_command>``
65     Specify the full path to the ``gcov`` command on the machine.
66     Default is the value of :variable:`CTEST_COVERAGE_COMMAND`.
68   ``GCOV_OPTIONS <options>...``
69     Specify options to be passed to gcov.  The ``gcov`` command
70     is run as ``gcov <options>... -o <gcov-dir> <file>.gcda``.
71     If not specified, the default option is just ``-b -x``.
73   ``GLOB``
74     .. versionadded:: 3.6
76     Recursively search for .gcda files in build_dir rather than
77     determining search locations by reading TargetDirectories.txt.
79   ``DELETE``
80     .. versionadded:: 3.6
82     Delete coverage files after they've been packaged into the .tar.
84   ``QUIET``
85     Suppress non-error messages that otherwise would have been
86     printed out by this function.
88   .. versionadded:: 3.3
89     Added support for the :variable:`CTEST_CUSTOM_COVERAGE_EXCLUDE` variable.
91 #]=======================================================================]
93 function(ctest_coverage_collect_gcov)
94   set(options QUIET GLOB DELETE)
95   set(oneValueArgs TARBALL SOURCE BUILD GCOV_COMMAND TARBALL_COMPRESSION)
96   set(multiValueArgs GCOV_OPTIONS)
97   cmake_parse_arguments(GCOV  "${options}" "${oneValueArgs}"
98     "${multiValueArgs}" "" ${ARGN} )
99   if(NOT DEFINED GCOV_TARBALL)
100     message(FATAL_ERROR
101       "TARBALL must be specified. for ctest_coverage_collect_gcov")
102   endif()
103   if(NOT DEFINED GCOV_SOURCE)
104     set(source_dir "${CTEST_SOURCE_DIRECTORY}")
105   else()
106     set(source_dir "${GCOV_SOURCE}")
107   endif()
108   if(NOT DEFINED GCOV_BUILD)
109     set(binary_dir "${CTEST_BINARY_DIRECTORY}")
110   else()
111     set(binary_dir "${GCOV_BUILD}")
112   endif()
113   if(NOT DEFINED GCOV_GCOV_COMMAND)
114     set(gcov_command "${CTEST_COVERAGE_COMMAND}")
115   else()
116     set(gcov_command "${GCOV_GCOV_COMMAND}")
117   endif()
118   if(NOT DEFINED GCOV_TARBALL_COMPRESSION)
119     set(GCOV_TARBALL_COMPRESSION "BZIP2")
120   elseif( GCOV_TARBALL_COMPRESSION AND
121       NOT GCOV_TARBALL_COMPRESSION MATCHES "^(GZIP|BZIP2|XZ|ZSTD|FROM_EXT)$")
122     message(FATAL_ERROR "TARBALL_COMPRESSION must be one of OFF, GZIP, "
123       "BZIP2, XZ, ZSTD, or FROM_EXT for ctest_coverage_collect_gcov")
124   endif()
125   # run gcov on each gcda file in the binary tree
126   set(gcda_files)
127   set(label_files)
128   if (GCOV_GLOB)
129       file(GLOB_RECURSE gfiles "${binary_dir}/*.gcda")
130       list(LENGTH gfiles len)
131       # if we have gcda files then also grab the labels file for that target
132       if(${len} GREATER 0)
133         file(GLOB_RECURSE lfiles RELATIVE ${binary_dir} "${binary_dir}/Labels.json")
134         list(APPEND gcda_files ${gfiles})
135         list(APPEND label_files ${lfiles})
136       endif()
137   else()
138     # look for gcda files in the target directories
139     # this will be faster and only look where the files will be
140     file(STRINGS "${binary_dir}/CMakeFiles/TargetDirectories.txt" target_dirs
141          ENCODING UTF-8)
142     foreach(target_dir ${target_dirs})
143       file(GLOB_RECURSE gfiles "${target_dir}/*.gcda")
144       list(LENGTH gfiles len)
145       # if we have gcda files then also grab the labels file for that target
146       if(${len} GREATER 0)
147         file(GLOB_RECURSE lfiles RELATIVE ${binary_dir}
148           "${target_dir}/Labels.json")
149         list(APPEND gcda_files ${gfiles})
150         list(APPEND label_files ${lfiles})
151       endif()
152     endforeach()
153   endif()
154   # return early if no coverage files were found
155   list(LENGTH gcda_files len)
156   if(len EQUAL 0)
157     if (NOT GCOV_QUIET)
158       message("ctest_coverage_collect_gcov: No .gcda files found, "
159         "ignoring coverage request.")
160     endif()
161     return()
162   endif()
163   # setup the dir for the coverage files
164   set(coverage_dir "${binary_dir}/Testing/CoverageInfo")
165   file(MAKE_DIRECTORY  "${coverage_dir}")
166   # run gcov, this will produce the .gcov files in the current
167   # working directory
168   if(NOT DEFINED GCOV_GCOV_OPTIONS)
169     set(GCOV_GCOV_OPTIONS -b -x)
170   endif()
171   if (GCOV_QUIET)
172     set(coverage_out_opts
173       OUTPUT_QUIET
174       ERROR_QUIET
175       )
176   else()
177     set(coverage_out_opts
178       OUTPUT_FILE "${coverage_dir}/gcov.log"
179       ERROR_FILE  "${coverage_dir}/gcov.log"
180       )
181   endif()
182   execute_process(COMMAND
183     ${gcov_command} ${GCOV_GCOV_OPTIONS} ${gcda_files}
184     RESULT_VARIABLE res
185     WORKING_DIRECTORY ${coverage_dir}
186     ${coverage_out_opts}
187     )
189   if (GCOV_DELETE)
190     file(REMOVE ${gcda_files})
191   endif()
193   if(NOT "${res}" EQUAL 0)
194     if (NOT GCOV_QUIET)
195       message(STATUS "Error running gcov: ${res}, see\n  ${coverage_dir}/gcov.log")
196     endif()
197   endif()
198   # create json file with project information
199   file(WRITE ${coverage_dir}/data.json
200     "{
201     \"Source\": \"${source_dir}\",
202     \"Binary\": \"${binary_dir}\"
204   # collect the gcov files
205   set(unfiltered_gcov_files)
206   file(GLOB_RECURSE unfiltered_gcov_files RELATIVE ${binary_dir} "${coverage_dir}/*.gcov")
208   # if CTEST_EXTRA_COVERAGE_GLOB was specified we search for files
209   # that might be uncovered
210   if (DEFINED CTEST_EXTRA_COVERAGE_GLOB)
211     set(uncovered_files)
212     foreach(search_entry IN LISTS CTEST_EXTRA_COVERAGE_GLOB)
213       if(NOT GCOV_QUIET)
214         message("Add coverage glob: ${search_entry}")
215       endif()
216       file(GLOB_RECURSE matching_files "${source_dir}/${search_entry}")
217       if (matching_files)
218         list(APPEND uncovered_files "${matching_files}")
219       endif()
220     endforeach()
221   endif()
223   set(gcov_files)
224   foreach(gcov_file ${unfiltered_gcov_files})
225     file(STRINGS ${binary_dir}/${gcov_file} first_line LIMIT_COUNT 1 ENCODING UTF-8)
227     set(is_excluded false)
228     if(first_line MATCHES "^        -:    0:Source:(.*)$")
229       set(source_file ${CMAKE_MATCH_1})
230     elseif(NOT GCOV_QUIET)
231       message(STATUS "Could not determine source file corresponding to: ${gcov_file}")
232     endif()
234     foreach(exclude_entry IN LISTS CTEST_CUSTOM_COVERAGE_EXCLUDE)
235       if(source_file MATCHES "${exclude_entry}")
236         set(is_excluded true)
238         if(NOT GCOV_QUIET)
239           message("Excluding coverage for: ${source_file} which matches ${exclude_entry}")
240         endif()
242         break()
243       endif()
244     endforeach()
246     get_filename_component(resolved_source_file "${source_file}" ABSOLUTE)
247     foreach(uncovered_file IN LISTS uncovered_files)
248       get_filename_component(resolved_uncovered_file "${uncovered_file}" ABSOLUTE)
249       if (resolved_uncovered_file STREQUAL resolved_source_file)
250         list(REMOVE_ITEM uncovered_files "${uncovered_file}")
251       endif()
252     endforeach()
254     if(NOT is_excluded)
255       list(APPEND gcov_files ${gcov_file})
256     endif()
257   endforeach()
259   foreach (uncovered_file ${uncovered_files})
260     # Check if this uncovered file should be excluded.
261     set(is_excluded false)
262     foreach(exclude_entry IN LISTS CTEST_CUSTOM_COVERAGE_EXCLUDE)
263       if(uncovered_file MATCHES "${exclude_entry}")
264         set(is_excluded true)
265         if(NOT GCOV_QUIET)
266           message("Excluding coverage for: ${uncovered_file} which matches ${exclude_entry}")
267         endif()
268         break()
269       endif()
270     endforeach()
271     if(is_excluded)
272       continue()
273     endif()
275     # Copy from source to binary dir, preserving any intermediate subdirectories.
276     get_filename_component(filename "${uncovered_file}" NAME)
277     get_filename_component(relative_path "${uncovered_file}" DIRECTORY)
278     string(REPLACE "${source_dir}" "" relative_path "${relative_path}")
279     if (relative_path)
280       # Strip leading slash.
281       string(SUBSTRING "${relative_path}" 1 -1 relative_path)
282     endif()
283     file(COPY ${uncovered_file} DESTINATION ${binary_dir}/uncovered/${relative_path})
284     if(relative_path)
285       list(APPEND uncovered_files_for_tar uncovered/${relative_path}/${filename})
286     else()
287       list(APPEND uncovered_files_for_tar uncovered/${filename})
288     endif()
289   endforeach()
291   # tar up the coverage info with the same date so that the md5
292   # sum will be the same for the tar file independent of file time
293   # stamps
294   string(REPLACE ";" "\n" gcov_files "${gcov_files}")
295   string(REPLACE ";" "\n" label_files "${label_files}")
296   string(REPLACE ";" "\n" uncovered_files_for_tar "${uncovered_files_for_tar}")
297   file(WRITE "${coverage_dir}/coverage_file_list.txt"
298     "${gcov_files}
299 ${coverage_dir}/data.json
300 ${label_files}
301 ${uncovered_files_for_tar}
304   # Prepare tar command line arguments
306   set(tar_opts "")
307   # Select data compression mode
308   if( GCOV_TARBALL_COMPRESSION STREQUAL "FROM_EXT")
309     if( GCOV_TARBALL MATCHES [[\.(tgz|tar.gz)$]] )
310       string(APPEND tar_opts "z")
311     elseif( GCOV_TARBALL MATCHES [[\.(txz|tar.xz)$]] )
312       string(APPEND tar_opts "J")
313     elseif( GCOV_TARBALL MATCHES [[\.(tbz|tar.bz)$]] )
314       string(APPEND tar_opts "j")
315     endif()
316   elseif(GCOV_TARBALL_COMPRESSION STREQUAL "GZIP")
317     string(APPEND tar_opts "z")
318   elseif(GCOV_TARBALL_COMPRESSION STREQUAL "XZ")
319     string(APPEND tar_opts "J")
320   elseif(GCOV_TARBALL_COMPRESSION STREQUAL "BZIP2")
321     string(APPEND tar_opts "j")
322   elseif(GCOV_TARBALL_COMPRESSION STREQUAL "ZSTD")
323     set(zstd_tar_opt "--zstd")
324   endif()
325   # Verbosity options
326   if(NOT GCOV_QUIET AND NOT tar_opts MATCHES v)
327     string(APPEND tar_opts "v")
328   endif()
329   # Prepend option 'c' specifying 'create'
330   string(PREPEND tar_opts "c")
331   # Append option 'f' so that the next argument is the filename
332   string(APPEND tar_opts "f")
334   execute_process(COMMAND
335     ${CMAKE_COMMAND} -E tar ${tar_opts} ${GCOV_TARBALL} ${zstd_tar_opt}
336     "--mtime=1970-01-01 0:0:0 UTC"
337     "--format=gnutar"
338     --files-from=${coverage_dir}/coverage_file_list.txt
339     WORKING_DIRECTORY ${binary_dir})
341   if (GCOV_DELETE)
342     foreach(gcov_file ${unfiltered_gcov_files})
343       file(REMOVE ${binary_dir}/${gcov_file})
344     endforeach()
345     file(REMOVE ${coverage_dir}/coverage_file_list.txt)
346     file(REMOVE ${coverage_dir}/data.json)
347     if (EXISTS ${binary_dir}/uncovered)
348       file(REMOVE ${binary_dir}/uncovered)
349     endif()
350   endif()
352 endfunction()