• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# This file is part of CMake-codecov.
2#
3# Copyright (c)
4#   2015-2017 RWTH Aachen University, Federal Republic of Germany
5#
6# See the LICENSE file in the package base directory for details
7#
8# Written by Alexander Haase, alexander.haase@rwth-aachen.de
9#
10
11
12# configuration
13set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data")
14set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init")
15set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture")
16set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html")
17
18
19
20
21# Search for Gcov which is used by Lcov.
22find_package(Gcov)
23
24
25
26
27# This function will add lcov evaluation for target <TNAME>. Only sources of
28# this target will be evaluated and no dependencies will be added. It will call
29# geninfo on any source file of <TNAME> once and store the info file in the same
30# directory.
31#
32# Note: This function is only a wrapper to define this function always, even if
33#   coverage is not supported by the compiler or disabled. This function must
34#   be defined here, because the module will be exited, if there is no coverage
35#   support by the compiler or it is disabled by the user.
36function (add_lcov_target TNAME)
37	if (LCOV_FOUND)
38		# capture initial coverage data
39		lcov_capture_initial_tgt(${TNAME})
40
41		# capture coverage data after execution
42		lcov_capture_tgt(${TNAME})
43	endif ()
44endfunction (add_lcov_target)
45
46
47
48
49# include required Modules
50include(FindPackageHandleStandardArgs)
51
52# Search for required lcov binaries.
53find_program(LCOV_BIN lcov)
54find_program(GENINFO_BIN geninfo)
55find_program(GENHTML_BIN genhtml)
56find_package_handle_standard_args(lcov
57	REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN
58)
59
60# enable genhtml C++ demangeling, if c++filt is found.
61set(GENHTML_CPPFILT_FLAG "")
62find_program(CPPFILT_BIN c++filt)
63if (NOT CPPFILT_BIN STREQUAL "")
64	set(GENHTML_CPPFILT_FLAG "--demangle-cpp")
65endif (NOT CPPFILT_BIN STREQUAL "")
66
67# enable no-external flag for lcov, if available.
68if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG)
69	set(FLAG "")
70	execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP)
71	string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}")
72	if (GENINFO_RES)
73		set(FLAG "--no-external")
74	endif ()
75
76	set(GENINFO_EXTERN_FLAG "${FLAG}"
77		CACHE STRING "Geninfo flag to exclude system sources.")
78endif ()
79
80# If Lcov was not found, exit module now.
81if (NOT LCOV_FOUND)
82	return()
83endif (NOT LCOV_FOUND)
84
85
86
87
88# Create directories to be used.
89file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT})
90file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE})
91
92set(LCOV_REMOVE_PATTERNS "")
93
94# This function will merge lcov files to a single target file. Additional lcov
95# flags may be set with setting LCOV_EXTRA_FLAGS before calling this function.
96function (lcov_merge_files OUTFILE ...)
97	# Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files.
98	list(REMOVE_AT ARGV 0)
99
100	# Generate merged file.
101	string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}")
102	add_custom_command(OUTPUT "${OUTFILE}.raw"
103		COMMAND cat ${ARGV} > ${OUTFILE}.raw
104		DEPENDS ${ARGV}
105		COMMENT "Generating ${FILE_REL}"
106	)
107
108	add_custom_command(OUTPUT "${OUTFILE}"
109		COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE}
110			--base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS}
111		COMMAND ${LCOV_BIN} --quiet -r ${OUTFILE} ${LCOV_REMOVE_PATTERNS}
112			--output-file ${OUTFILE} ${LCOV_EXTRA_FLAGS}
113		DEPENDS ${OUTFILE}.raw
114		COMMENT "Post-processing ${FILE_REL}"
115	)
116endfunction ()
117
118
119
120
121# Add a new global target to generate initial coverage reports for all targets.
122# This target will be used to generate the global initial info file, which is
123# used to gather even empty report data.
124if (NOT TARGET lcov-capture-init)
125	add_custom_target(lcov-capture-init)
126	set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "")
127endif (NOT TARGET lcov-capture-init)
128
129
130# This function will add initial capture of coverage data for target <TNAME>,
131# which is needed to get also data for objects, which were not loaded at
132# execution time. It will call geninfo for every source file of <TNAME> once and
133# store the info file in the same directory.
134function (lcov_capture_initial_tgt TNAME)
135	# We don't have to check, if the target has support for coverage, thus this
136	# will be checked by add_coverage_target in Findcoverage.cmake. Instead we
137	# have to determine which gcov binary to use.
138	get_target_property(TSOURCES ${TNAME} SOURCES)
139	set(SOURCES "")
140	set(TCOMPILER "")
141	foreach (FILE ${TSOURCES})
142		codecov_path_of_source(${FILE} FILE)
143		if (NOT "${FILE}" STREQUAL "")
144			codecov_lang_of_source(${FILE} LANG)
145			if (NOT "${LANG}" STREQUAL "")
146				list(APPEND SOURCES "${FILE}")
147				set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
148			endif ()
149		endif ()
150	endforeach ()
151
152	# If no gcov binary was found, coverage data can't be evaluated.
153	if (NOT GCOV_${TCOMPILER}_BIN)
154		message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
155		return()
156	endif ()
157
158	set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
159	set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")
160
161
162	set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
163	set(GENINFO_FILES "")
164	foreach(FILE ${SOURCES})
165		# generate empty coverage files
166		set(OUTFILE "${TDIR}/${FILE}.info.init")
167		list(APPEND GENINFO_FILES ${OUTFILE})
168
169		add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN}
170				--quiet --base-directory ${PROJECT_SOURCE_DIR} --initial
171				--gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE}
172				${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno
173			DEPENDS ${TNAME}
174			COMMENT "Capturing initial coverage data for ${FILE}"
175		)
176	endforeach()
177
178	# Concatenate all files generated by geninfo to a single file per target.
179	set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info")
180	set(LCOV_EXTRA_FLAGS "--initial")
181	lcov_merge_files("${OUTFILE}" ${GENINFO_FILES})
182	add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE})
183
184	# add geninfo file generation to global lcov-geninfo target
185	add_dependencies(lcov-capture-init ${TNAME}-capture-init)
186	set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}"
187		"${OUTFILE}" CACHE INTERNAL ""
188	)
189endfunction (lcov_capture_initial_tgt)
190
191
192# This function will generate the global info file for all targets. It has to be
193# called after all other CMake functions in the root CMakeLists.txt file, to get
194# a full list of all targets that generate coverage data.
195function (lcov_capture_initial)
196	# Skip this function (and do not create the following targets), if there are
197	# no input files.
198	if ("${LCOV_CAPTURE_INIT_FILES}" STREQUAL "")
199		return()
200	endif ()
201
202	# Add a new target to merge the files of all targets.
203	set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info")
204	lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES})
205	add_custom_target(lcov-geninfo-init ALL	DEPENDS ${OUTFILE}
206		lcov-capture-init
207	)
208endfunction (lcov_capture_initial)
209
210
211
212
213# Add a new global target to generate coverage reports for all targets. This
214# target will be used to generate the global info file.
215if (NOT TARGET lcov-capture)
216	add_custom_target(lcov-capture)
217	set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "")
218endif (NOT TARGET lcov-capture)
219
220
221# This function will add capture of coverage data for target <TNAME>, which is
222# needed to get also data for objects, which were not loaded at execution time.
223# It will call geninfo for every source file of <TNAME> once and store the info
224# file in the same directory.
225function (lcov_capture_tgt TNAME)
226	# We don't have to check, if the target has support for coverage, thus this
227	# will be checked by add_coverage_target in Findcoverage.cmake. Instead we
228	# have to determine which gcov binary to use.
229	get_target_property(TSOURCES ${TNAME} SOURCES)
230	set(SOURCES "")
231	set(TCOMPILER "")
232	foreach (FILE ${TSOURCES})
233		codecov_path_of_source(${FILE} FILE)
234		if (NOT "${FILE}" STREQUAL "")
235			codecov_lang_of_source(${FILE} LANG)
236			if (NOT "${LANG}" STREQUAL "")
237				list(APPEND SOURCES "${FILE}")
238				set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
239			endif ()
240		endif ()
241	endforeach ()
242
243	# If no gcov binary was found, coverage data can't be evaluated.
244	if (NOT GCOV_${TCOMPILER}_BIN)
245		message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
246		return()
247	endif ()
248
249	set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
250	set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")
251
252
253	set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
254	set(GENINFO_FILES "")
255	foreach(FILE ${SOURCES})
256		# Generate coverage files. If no .gcda file was generated during
257		# execution, the empty coverage file will be used instead.
258		set(OUTFILE "${TDIR}/${FILE}.info")
259		list(APPEND GENINFO_FILES ${OUTFILE})
260
261		add_custom_command(OUTPUT ${OUTFILE}
262			COMMAND test -f "${TDIR}/${FILE}.gcda"
263				&& ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory
264					${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN}
265					--output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG}
266					${TDIR}/${FILE}.gcda
267				|| cp ${OUTFILE}.init ${OUTFILE}
268			DEPENDS ${TNAME} ${TNAME}-capture-init
269			COMMENT "Capturing coverage data for ${FILE}"
270		)
271	endforeach()
272
273	# Concatenate all files generated by geninfo to a single file per target.
274	set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info")
275	lcov_merge_files("${OUTFILE}" ${GENINFO_FILES})
276	add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE})
277
278	# add geninfo file generation to global lcov-capture target
279	add_dependencies(lcov-capture ${TNAME}-geninfo)
280	set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL
281		""
282	)
283
284	# Add target for generating html output for this target only.
285	file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME})
286	add_custom_target(${TNAME}-genhtml
287		COMMAND ${GENHTML_BIN} --quiet --sort --prefix ${PROJECT_SOURCE_DIR}
288			--baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info
289			--output-directory ${LCOV_HTML_PATH}/${TNAME}
290			--title "${CMAKE_PROJECT_NAME} - target ${TNAME}"
291			${GENHTML_CPPFILT_FLAG} ${OUTFILE}
292		DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init
293	)
294endfunction (lcov_capture_tgt)
295
296
297# This function will generate the global info file for all targets. It has to be
298# called after all other CMake functions in the root CMakeLists.txt file, to get
299# a full list of all targets that generate coverage data.
300function (lcov_capture)
301	# Skip this function (and do not create the following targets), if there are
302	# no input files.
303	if ("${LCOV_CAPTURE_FILES}" STREQUAL "")
304		return()
305	endif ()
306
307	# Add a new target to merge the files of all targets.
308	set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info")
309	lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES})
310	add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture)
311
312	# Add a new global target for all lcov targets. This target could be used to
313	# generate the lcov html output for the whole project instead of calling
314	# <TARGET>-geninfo and <TARGET>-genhtml for each target. It will also be
315	# used to generate a html site for all project data together instead of one
316	# for each target.
317	if (NOT TARGET lcov)
318		file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/all_targets)
319		add_custom_target(lcov
320			COMMAND ${GENHTML_BIN} --quiet --sort
321				--baseline-file ${LCOV_DATA_PATH_INIT}/all_targets.info
322				--output-directory ${LCOV_HTML_PATH}/all_targets
323				--title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}"
324				${GENHTML_CPPFILT_FLAG} ${OUTFILE}
325			DEPENDS lcov-geninfo-init lcov-geninfo
326		)
327	endif ()
328endfunction (lcov_capture)
329
330
331
332
333# Add a new global target to generate the lcov html report for the whole project
334# instead of calling <TARGET>-genhtml for each target (to create an own report
335# for each target). Instead of the lcov target it does not require geninfo for
336# all targets, so you have to call <TARGET>-geninfo to generate the info files
337# the targets you'd like to have in your report or lcov-geninfo for generating
338# info files for all targets before calling lcov-genhtml.
339file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets)
340if (NOT TARGET lcov-genhtml)
341	add_custom_target(lcov-genhtml
342		COMMAND ${GENHTML_BIN}
343			--quiet
344			--output-directory ${LCOV_HTML_PATH}/selected_targets
345			--title \"${CMAKE_PROJECT_NAME} - targets  `find
346				${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name
347				\"all_targets.info\" -exec basename {} .info \\\;`\"
348			--prefix ${PROJECT_SOURCE_DIR}
349			--sort
350			${GENHTML_CPPFILT_FLAG}
351			`find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name
352				\"all_targets.info\"`
353	)
354endif (NOT TARGET lcov-genhtml)
355