summaryrefslogtreecommitdiff
path: root/CMAKE/Findcodecov.cmake
blob: 112507846f665d9667b745e2082ca14c69011f00 (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
# This file is part of CMake-codecov.
#
# Copyright (c)
#   2015-2016 RWTH Aachen University, Federal Republic of Germany
#
# See the LICENSE file in the package base directory for details
#
# Written by Alexander Haase, alexander.haase@rwth-aachen.de
#


# Add an option to choose, if coverage should be enabled or not. If enabled
# marked targets will be build with coverage support and appropriate targets
# will be added. If disabled coverage will be ignored for *ALL* targets.
option(ENABLE_COVERAGE "Enable coverage build." OFF)

set(COVERAGE_FLAG_CANDIDATES
	# gcc and clang
	"-O0 -g -fprofile-arcs -ftest-coverage"

	# gcc and clang fallback
	"-O0 -g --coverage"
)


# To avoid error messages about CMP0051, this policy will be set to new. There
# will be no problem, as TARGET_OBJECTS generator expressions will be filtered
# with a regular expression from the sources.
if (POLICY CMP0051)
	cmake_policy(SET CMP0051 NEW)
endif()


# Add coverage support for target ${TNAME} and register target for coverage
# evaluation. If coverage is disabled or not supported, this function will
# simply do nothing.
#
# 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_coverage TNAME)
	# only add coverage for target, if coverage is support and enabled.
	if (ENABLE_COVERAGE)
		foreach (TNAME ${ARGV})
			add_coverage_target(${TNAME})
		endforeach ()
	endif ()
endfunction (add_coverage)


# Add global target to gather coverage information after all targets have been
# added. Other evaluation functions could be added here, after checks for the
# specific module have been passed.
#
# 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 (coverage_evaluate)
	# add lcov evaluation
	if (LCOV_FOUND)
		lcov_capture_initial()
		lcov_capture()
	endif (LCOV_FOUND)
endfunction ()


# Exit this module, if coverage is disabled. add_coverage is defined before this
# return, so this module can be exited now safely without breaking any build-
# scripts.
if (NOT ENABLE_COVERAGE)
	return()
endif ()




# Find the reuired flags foreach language.
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY})

get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach (LANG ${ENABLED_LANGUAGES})
	# Coverage flags are not dependend on language, but the used compiler. So
	# instead of searching flags foreach language, search flags foreach compiler
	# used.
	set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
	if (NOT COVERAGE_${COMPILER}_FLAGS)
		foreach (FLAG ${COVERAGE_FLAG_CANDIDATES})
			if(NOT CMAKE_REQUIRED_QUIET)
				message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]")
			endif()

			set(CMAKE_REQUIRED_FLAGS "${FLAG}")
			unset(COVERAGE_FLAG_DETECTED CACHE)

			if (${LANG} STREQUAL "C")
				include(CheckCCompilerFlag)
				check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED)

			elseif (${LANG} STREQUAL "CXX")
				include(CheckCXXCompilerFlag)
				check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED)

			elseif (${LANG} STREQUAL "Fortran")
				# CheckFortranCompilerFlag was introduced in CMake 3.x. To be
				# compatible with older Cmake versions, we will check if this
				# module is present before we use it. Otherwise we will define
				# Fortran coverage support as not available.
				include(CheckFortranCompilerFlag OPTIONAL
					RESULT_VARIABLE INCLUDED)
				if (INCLUDED)
					check_fortran_compiler_flag("${FLAG}"
						COVERAGE_FLAG_DETECTED)
				elseif (NOT CMAKE_REQUIRED_QUIET)
					message("-- Performing Test COVERAGE_FLAG_DETECTED")
					message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed"
						" (Check not supported)")
				endif ()
			endif()

			if (COVERAGE_FLAG_DETECTED)
				set(COVERAGE_${COMPILER}_FLAGS "${FLAG}"
					CACHE STRING "${COMPILER} flags for code coverage.")
				mark_as_advanced(COVERAGE_${COMPILER}_FLAGS)
				break()
			endif ()
		endforeach ()
	endif ()
endforeach ()

set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})




# Helper function to get the language of a source file.
function (codecov_lang_of_source FILE RETURN_VAR)
	get_filename_component(FILE_EXT "${FILE}" EXT)
	string(TOLOWER "${FILE_EXT}" FILE_EXT)
	string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)

	get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
	foreach (LANG ${ENABLED_LANGUAGES})
		list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
		if (NOT ${TEMP} EQUAL -1)
			set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
			return()
		endif ()
	endforeach()

	set(${RETURN_VAR} "" PARENT_SCOPE)
endfunction ()


# Helper function to get the relative path of the source file destination path.
# This path is needed by FindGcov and FindLcov cmake files to locate the
# captured data.
function (codecov_path_of_source FILE RETURN_VAR)
	string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE})

	# If expression was found, SOURCEFILE is a generator-expression for an
	# object library. Currently we found no way to call this function automatic
	# for the referenced target, so it must be called in the directoryso of the
	# object library definition.
	if (NOT "${_source}" STREQUAL "")
		set(${RETURN_VAR} "" PARENT_SCOPE)
		return()
	endif ()


	string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}")
	if(IS_ABSOLUTE ${FILE})
		file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE})
	endif()

	# get the right path for file
	string(REPLACE ".." "__" PATH "${FILE}")

	set(${RETURN_VAR} "${PATH}" PARENT_SCOPE)
endfunction()




# Add coverage support for target ${TNAME} and register target for coverage
# evaluation.
function(add_coverage_target TNAME)
	# Check if all sources for target use the same compiler. If a target uses
	# e.g. C and Fortran mixed and uses different compilers (e.g. clang and
	# gfortran) this can trigger huge problems, because different compilers may
	# use different implementations for code coverage.
	get_target_property(TSOURCES ${TNAME} SOURCES)
	set(TARGET_COMPILER "")
	set(ADDITIONAL_FILES "")
	foreach (FILE ${TSOURCES})
		# If expression was found, FILE is a generator-expression for an object
		# library. Object libraries will be ignored.
		string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
		if ("${_file}" STREQUAL "")
			codecov_lang_of_source(${FILE} LANG)
			if (LANG)
				list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID})

				list(APPEND ADDITIONAL_FILES "${FILE}.gcno")
				list(APPEND ADDITIONAL_FILES "${FILE}.gcda")
			endif ()
		endif ()
	endforeach ()

	list(REMOVE_DUPLICATES TARGET_COMPILER)
	list(LENGTH TARGET_COMPILER NUM_COMPILERS)

	if (NUM_COMPILERS GREATER 1)
		message(AUTHOR_WARNING "Coverage disabled for target ${TNAME} because "
			"it will be compiled by different compilers.")
		return()

	elseif ((NUM_COMPILERS EQUAL 0) OR
		(NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS"))
		message(AUTHOR_WARNING "Coverage disabled for target ${TNAME} "
			"because there is no sanitizer available for target sources.")
		return()
	endif()


	# enable coverage for target
	set_property(TARGET ${TNAME} APPEND_STRING
		PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}")
	set_property(TARGET ${TNAME} APPEND_STRING
		PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}")


	# Add gcov files generated by compiler to clean target.
	set(CLEAN_FILES "")
	foreach (FILE ${ADDITIONAL_FILES})
		codecov_path_of_source(${FILE} FILE)
		list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}")
	endforeach()

	set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
		"${CLEAN_FILES}")


	add_gcov_target(${TNAME})
	add_lcov_target(${TNAME})
endfunction(add_coverage_target)




# Include modules for parsing the collected data and output it in a readable
# format (like gcov and lcov).
find_package(Gcov)
find_package(Lcov)