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 ------------------------
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
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
29 ctest_coverage_collect_gcov(TARBALL <tarfile>
30 [SOURCE <source_dir>][BUILD <build_dir>]
31 [GCOV_COMMAND <gcov_command>]
32 [GCOV_OPTIONS <options>...]
35 Run gcov and package a tar file for CDash. The options are:
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`.
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``.
76 Recursively search for .gcda files in build_dir rather than
77 determining search locations by reading TargetDirectories.txt.
82 Delete coverage files after they've been packaged into the .tar.
85 Suppress non-error messages that otherwise would have been
86 printed out by this function.
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)
101 "TARBALL must be specified. for ctest_coverage_collect_gcov")
103 if(NOT DEFINED GCOV_SOURCE)
104 set(source_dir "${CTEST_SOURCE_DIRECTORY}")
106 set(source_dir "${GCOV_SOURCE}")
108 if(NOT DEFINED GCOV_BUILD)
109 set(binary_dir "${CTEST_BINARY_DIRECTORY}")
111 set(binary_dir "${GCOV_BUILD}")
113 if(NOT DEFINED GCOV_GCOV_COMMAND)
114 set(gcov_command "${CTEST_COVERAGE_COMMAND}")
116 set(gcov_command "${GCOV_GCOV_COMMAND}")
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")
125 # run gcov on each gcda file in the binary tree
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
133 file(GLOB_RECURSE lfiles RELATIVE ${binary_dir} "${binary_dir}/Labels.json")
134 list(APPEND gcda_files ${gfiles})
135 list(APPEND label_files ${lfiles})
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
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
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})
154 # return early if no coverage files were found
155 list(LENGTH gcda_files len)
158 message("ctest_coverage_collect_gcov: No .gcda files found, "
159 "ignoring coverage request.")
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
168 if(NOT DEFINED GCOV_GCOV_OPTIONS)
169 set(GCOV_GCOV_OPTIONS -b -x)
172 set(coverage_out_opts
177 set(coverage_out_opts
178 OUTPUT_FILE "${coverage_dir}/gcov.log"
179 ERROR_FILE "${coverage_dir}/gcov.log"
182 execute_process(COMMAND
183 ${gcov_command} ${GCOV_GCOV_OPTIONS} ${gcda_files}
185 WORKING_DIRECTORY ${coverage_dir}
190 file(REMOVE ${gcda_files})
193 if(NOT "${res}" EQUAL 0)
195 message(STATUS "Error running gcov: ${res}, see\n ${coverage_dir}/gcov.log")
198 # create json file with project information
199 file(WRITE ${coverage_dir}/data.json
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)
212 foreach(search_entry IN LISTS CTEST_EXTRA_COVERAGE_GLOB)
214 message("Add coverage glob: ${search_entry}")
216 file(GLOB_RECURSE matching_files "${source_dir}/${search_entry}")
218 list(APPEND uncovered_files "${matching_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}")
234 foreach(exclude_entry IN LISTS CTEST_CUSTOM_COVERAGE_EXCLUDE)
235 if(source_file MATCHES "${exclude_entry}")
236 set(is_excluded true)
239 message("Excluding coverage for: ${source_file} which matches ${exclude_entry}")
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}")
255 list(APPEND gcov_files ${gcov_file})
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)
266 message("Excluding coverage for: ${uncovered_file} which matches ${exclude_entry}")
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}")
280 # Strip leading slash.
281 string(SUBSTRING "${relative_path}" 1 -1 relative_path)
283 file(COPY ${uncovered_file} DESTINATION ${binary_dir}/uncovered/${relative_path})
285 list(APPEND uncovered_files_for_tar uncovered/${relative_path}/${filename})
287 list(APPEND uncovered_files_for_tar uncovered/${filename})
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
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"
299 ${coverage_dir}/data.json
301 ${uncovered_files_for_tar}
304 # Prepare tar command line arguments
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")
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")
326 if(NOT GCOV_QUIET AND NOT tar_opts MATCHES v)
327 string(APPEND tar_opts "v")
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"
338 --files-from=${coverage_dir}/coverage_file_list.txt
339 WORKING_DIRECTORY ${binary_dir})
342 foreach(gcov_file ${unfiltered_gcov_files})
343 file(REMOVE ${binary_dir}/${gcov_file})
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)