summaryrefslogtreecommitdiff
path: root/cmake/FindLcov.cmake
blob: f92fca31a9981834ab5859b7a840ed03908213ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# This file is part of CMake-codecov.
#
# CMake-codecov is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful,but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see
#
#  http://www.gnu.org/licenses/
#
#
# Copyright (c)
#   2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# Written by Alexander Haase, alexander.haase@rwth-aachen.de
#


# configuration
set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data")
set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init")
set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture")
set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html")




# Search for Gcov which is used by Lcov.
find_package(Gcov)




# This function will add lcov evaluation for target <TNAME>. Only sources of
# this target will be evaluated and no dependencies will be added. It will call
# geninfo on any source file of <TNAME> once and store the info file in the same
# directory.
#
# Note: This function is only a wrapper to define this function always, even if
#   coverage is not supported by the compiler or disabled. This function must
#   be defined here, because the module will be exited, if there is no coverage
#   support by the compiler or it is disabled by the user.
function (add_lcov_target TNAME)
	if (LCOV_FOUND)
		# capture initial coverage data
		lcov_capture_initial_tgt(${TNAME})

		# capture coverage data after execution
		lcov_capture_tgt(${TNAME})
	endif ()
endfunction (add_lcov_target)




# include required Modules
include(FindPackageHandleStandardArgs)

# Search for required lcov binaries.
find_program(LCOV_BIN lcov)
find_program(GENINFO_BIN geninfo)
find_program(GENHTML_BIN genhtml)
find_package_handle_standard_args(lcov
	REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN
)

# enable genhtml C++ demangeling, if c++filt is found.
set(GENHTML_CPPFILT_FLAG "")
find_program(CPPFILT_BIN c++filt)
if (NOT CPPFILT_BIN STREQUAL "")
	set(GENHTML_CPPFILT_FLAG "--demangle-cpp")
endif (NOT CPPFILT_BIN STREQUAL "")

# enable no-external flag for lcov, if available.
if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG)
	set(FLAG "")
	execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP)
	string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}")
	if (GENINFO_RES)
		set(FLAG "--no-external")
	endif ()

	set(GENINFO_EXTERN_FLAG "${FLAG}"
		CACHE STRING "Geninfo flag to exclude system sources.")
endif ()

# If Lcov was not found, exit module now.
if (NOT LCOV_FOUND)
	return()
endif (NOT LCOV_FOUND)




# Create directories to be used.
file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT})
file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE})


# This function will merge lcov files to a single target file. Additional lcov
# flags may be set with setting LCOV_EXTRA_FLAGS before calling this function.
function (lcov_merge_files OUTFILE ...)
	# Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files.
	list(REMOVE_AT ARGV 0)

	# Generate merged file.
	string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}")
	add_custom_command(OUTPUT "${OUTFILE}.raw"
		COMMAND cat ${ARGV} > ${OUTFILE}.raw
		DEPENDS ${ARGV}
		COMMENT "Generating ${FILE_REL}"
	)

	add_custom_command(OUTPUT "${OUTFILE}"
		COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE}
			--base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS}
		DEPENDS ${OUTFILE}.raw
		COMMENT "Post-processing ${FILE_REL}"
	)
endfunction ()




# Add a new global target to generate initial coverage reports for all targets.
# This target will be used to generate the global initial info file, which is
# used to gather even empty report data.
if (NOT TARGET lcov-capture-init)
	add_custom_target(lcov-capture-init)
	set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "")
endif (NOT TARGET lcov-capture-init)


# This function will add initial capture of coverage data for target <TNAME>,
# which is needed to get also data for objects, which were not loaded at
# execution time. It will call geninfo for every source file of <TNAME> once and
# store the info file in the same directory.
function (lcov_capture_initial_tgt TNAME)
	# We don't have to check, if the target has support for coverage, thus this
	# will be checked by add_coverage_target in Findcoverage.cmake. Instead we
	# have to determine which gcov binary to use.
	get_target_property(TSOURCES ${TNAME} SOURCES)
	set(SOURCES "")
	set(TCOMPILER "")
	foreach (FILE ${TSOURCES})
		codecov_path_of_source(${FILE} FILE)
		if (NOT "${FILE}" STREQUAL "")
			codecov_lang_of_source(${FILE} LANG)
			if (NOT "${LANG}" STREQUAL "")
				list(APPEND SOURCES "${FILE}")
				set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
			endif ()
		endif ()
	endforeach ()

	# If no gcov binary was found, coverage data can't be evaluated.
	if (NOT GCOV_${TCOMPILER}_BIN)
		message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
		return()
	endif ()

	set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
	set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")


	set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
	set(GENINFO_FILES "")
	foreach(FILE ${SOURCES})
		# generate empty coverage files
		set(OUTFILE "${TDIR}/${FILE}.info.init")
		list(APPEND GENINFO_FILES ${OUTFILE})

		add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN}
				--quiet --base-directory ${PROJECT_SOURCE_DIR} --initial
				--gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE}
				${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno
			DEPENDS ${TNAME}
			COMMENT "Capturing initial coverage data for ${FILE}"
		)
	endforeach()

	# Concatenate all files generated by geninfo to a single file per target.
	set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info")
	set(LCOV_EXTRA_FLAGS "--initial")
	lcov_merge_files("${OUTFILE}" ${GENINFO_FILES})
	add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE})

	# add geninfo file generation to global lcov-geninfo target
	add_dependencies(lcov-capture-init ${TNAME}-capture-init)
	set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}"
		"${OUTFILE}" CACHE INTERNAL ""
	)
endfunction (lcov_capture_initial_tgt)


# This function will generate the global info file for all targets. It has to be
# called after all other CMake functions in the root CMakeLists.txt file, to get
# a full list of all targets that generate coverage data.
function (lcov_capture_initial)
	# Skip this function (and do not create the following targets), if there are
	# no input files.
	if ("${LCOV_CAPTURE_INIT_FILES}" STREQUAL "")
		return()
	endif ()

	# Add a new target to merge the files of all targets.
	set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info")
	lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES})
	add_custom_target(lcov-geninfo-init ALL	DEPENDS ${OUTFILE}
		lcov-capture-init
	)
endfunction (lcov_capture_initial)




# Add a new global target to generate coverage reports for all targets. This
# target will be used to generate the global info file.
if (NOT TARGET lcov-capture)
	add_custom_target(lcov-capture)
	set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "")
endif (NOT TARGET lcov-capture)


# This function will add capture of coverage data for target <TNAME>, which is
# needed to get also data for objects, which were not loaded at execution time.
# It will call geninfo for every source file of <TNAME> once and store the info
# file in the same directory.
function (lcov_capture_tgt TNAME)
	# We don't have to check, if the target has support for coverage, thus this
	# will be checked by add_coverage_target in Findcoverage.cmake. Instead we
	# have to determine which gcov binary to use.
	get_target_property(TSOURCES ${TNAME} SOURCES)
	set(SOURCES "")
	set(TCOMPILER "")
	foreach (FILE ${TSOURCES})
		codecov_path_of_source(${FILE} FILE)
		if (NOT "${FILE}" STREQUAL "")
			codecov_lang_of_source(${FILE} LANG)
			if (NOT "${LANG}" STREQUAL "")
				list(APPEND SOURCES "${FILE}")
				set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
			endif ()
		endif ()
	endforeach ()

	# If no gcov binary was found, coverage data can't be evaluated.
	if (NOT GCOV_${TCOMPILER}_BIN)
		message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
		return()
	endif ()

	set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
	set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")


	set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
	set(GENINFO_FILES "")
	foreach(FILE ${SOURCES})
		# Generate coverage files. If no .gcda file was generated during
		# execution, the empty coverage file will be used instead.
		set(OUTFILE "${TDIR}/${FILE}.info")
		list(APPEND GENINFO_FILES ${OUTFILE})

		add_custom_command(OUTPUT ${OUTFILE}
			COMMAND test -f "${TDIR}/${FILE}.gcda"
				&& ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory
					${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN}
					--output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG}
					${TDIR}/${FILE}.gcda
				|| cp ${OUTFILE}.init ${OUTFILE}
			DEPENDS ${TNAME} ${TNAME}-capture-init
			COMMENT "Capturing coverage data for ${FILE}"
		)
	endforeach()

	# Concatenate all files generated by geninfo to a single file per target.
	set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info")
	lcov_merge_files("${OUTFILE}" ${GENINFO_FILES})
	add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE})

	# add geninfo file generation to global lcov-capture target
	add_dependencies(lcov-capture ${TNAME}-geninfo)
	set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL
		""
	)

	# Add target for generating html output for this target only.
	file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME})
	add_custom_target(${TNAME}-genhtml
		COMMAND ${GENHTML_BIN} --quiet --sort --prefix ${PROJECT_SOURCE_DIR}
			--baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info
			--output-directory ${LCOV_HTML_PATH}/${TNAME}
			--title "${CMAKE_PROJECT_NAME} - target ${TNAME}"
			${GENHTML_CPPFILT_FLAG} ${OUTFILE}
		DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init
	)
endfunction (lcov_capture_tgt)


# This function will generate the global info file for all targets. It has to be
# called after all other CMake functions in the root CMakeLists.txt file, to get
# a full list of all targets that generate coverage data.
function (lcov_capture)
	# Skip this function (and do not create the following targets), if there are
	# no input files.
	if ("${LCOV_CAPTURE_FILES}" STREQUAL "")
		return()
	endif ()

	# Add a new target to merge the files of all targets.
	set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info")
	lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES})
	add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture)

	# Add a new global target for all lcov targets. This target could be used to
	# generate the lcov html output for the whole project instead of calling
	# <TARGET>-geninfo and <TARGET>-genhtml for each target. It will also be
	# used to generate a html site for all project data together instead of one
	# for each target.
	if (NOT TARGET lcov)
		file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/all_targets)
		add_custom_target(lcov
			COMMAND ${GENHTML_BIN} --quiet --sort
				--baseline-file ${LCOV_DATA_PATH_INIT}/all_targets.info
				--output-directory ${LCOV_HTML_PATH}/all_targets
				--title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}"
				${GENHTML_CPPFILT_FLAG} ${OUTFILE}
			DEPENDS lcov-geninfo-init lcov-geninfo
		)
	endif ()
endfunction (lcov_capture)




# Add a new global target to generate the lcov html report for the whole project
# instead of calling <TARGET>-genhtml for each target (to create an own report
# for each target). Instead of the lcov target it does not require geninfo for
# all targets, so you have to call <TARGET>-geninfo to generate the info files
# the targets you'd like to have in your report or lcov-geninfo for generating
# info files for all targets before calling lcov-genhtml.
file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets)
if (NOT TARGET lcov-genhtml)
	add_custom_target(lcov-genhtml
		COMMAND ${GENHTML_BIN}
			--quiet
			--output-directory ${LCOV_HTML_PATH}/selected_targets
			--title \"${CMAKE_PROJECT_NAME} - targets  `find
				${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name
				\"all_targets.info\" -exec basename {} .info \\\;`\"
			--prefix ${PROJECT_SOURCE_DIR}
			--sort
			${GENHTML_CPPFILT_FLAG}
			`find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name
				\"all_targets.info\"`
	)
endif (NOT TARGET lcov-genhtml)