blob: a66649c8ee5777dc0ad2de14c49ebc371e06a7d0 [file] [log] [blame]
Imre Kisa21712e2019-10-08 12:56:59 +02001#
Imre Kis0e65dd62022-04-20 17:52:35 +02002# Copyright (c) 2020-2022, Arm Limited. All rights reserved.
Imre Kisa21712e2019-10-08 12:56:59 +02003#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6
7#[===[.rst:
8Coverage CMake module
9---------------------
10
11Control flow
12^^^^^^^^^^^^
13
14Using the code coverage feature of the system starts with including
15``Coverage`` module. This will implicitly check if all the requirements for
16generating coverage are fulfilled. This includes checking the following
17conditions.
18
19- Compiler is GCC
20- lcov executables exist
21- ``c-picker-coverage-mapper`` is available
22
23As the next step it sets the compiler flags to make GCC to generate binaries
24with coverage information.
25
26
27Variables
28^^^^^^^^^
29
30The module sets the following variables while it's checking its prerequisites.
31
32.. cmake:variable:: LCOV_COMMAND
33
34Path of lcov executable
35
36.. cmake:variable:: GENHTML_COMMAND
37
38Path of genhtml executable which is part of the lcov package.
39
40.. cmake:variable:: CPICKER_COVERAGE_MAPPER_COMMAND
41
42Path of ``c-picker-coverage-mapper`` executable which is provided by c-picker
43pip package.
44
45
46Functions
47^^^^^^^^^
48
49The module also contains functions for setting up the coverage feature.
50
51#]===]
52
53include_guard(DIRECTORY)
54
55# Checking GCC
56if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
57 message(FATAL_ERROR "Coverage measurement is only supported when using GCC")
58endif()
59
60# Checking lcov
61find_program(LCOV_COMMAND "lcov")
62if (NOT LCOV_COMMAND)
63 message(FATAL_ERROR "Please install lcov")
64endif()
65
66# Checking c-picker-coverage-mapper
67find_program(CPICKER_COVERAGE_MAPPER_COMMAND "c-picker-coverage-mapper")
68if (NOT CPICKER_COVERAGE_MAPPER_COMMAND)
69 message(FATAL_ERROR "Please install c-picker-coverage-mapper using pip (part of c-picker)")
70endif()
71
72# Checking genhtml
73find_program(GENHTML_COMMAND "genhtml")
74if (NOT GENHTML_COMMAND)
75 message(FATAL_ERROR "Please install genhtml with genhtml (part of lcov)")
76endif()
77
78# Including this file enables code coverage measurement by adding the necessary compiler and
79# linker flags.
80set(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
81set(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage -fno-exceptions")
82set(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage")
83
84# Adding custom targets
85add_custom_target(coverage)
86add_custom_target(coverage_report)
87
88# Adds a file to the dependency list of the target by inserting an accessory
89# custom target. The name of the custom target is properly escaped.
90function(add_coverage_dependency)
91 set(_OPTIONS_ARGS)
92 set(_ONE_VALUE_ARGS TARGET DEPENDS)
93 set(_MULTI_VALUE_ARGS)
94 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
95
96 set(TARGET ${_MY_PARAMS_TARGET})
97 set(DEPENDS ${_MY_PARAMS_DEPENDS})
98
99 string(REGEX REPLACE "\\/" "_" CUSTOM_TARGET_SUFFIX ${DEPENDS})
100
101 add_custom_target(${TARGET}_target_${CUSTOM_TARGET_SUFFIX} DEPENDS ${DEPENDS})
102 add_dependencies(${TARGET} ${TARGET}_target_${CUSTOM_TARGET_SUFFIX})
103endfunction()
104
105#[===[.rst:
106.. cmake:command:: coverage_generate
107
108 .. code-block:: cmake
109
110 coverage_generate(
111 NAME test_name
112 SOURCE_DIR source_directory
113 BINARY_DIR binary_directory
114 OUTPUT_FILE output_file
115 )
116
117 The function outputs an lcov info file for further processing. It also handles
118 the remapping of the coverage of the c-picker generated files.
119
120 Control flow:
121
122 1. Running the ``lcov`` command for collecting the coverage data from the
123 available ``.gcda`` and ``.gcno`` files in the ``BINARY_DIR``.
124
125 2. The output of previous step is processed by ``c-picker-coverage-mapper``.
126 This will remap the coverage of files in ``CPICKER_CACHE_PATH`` to the
127 original source files.
128
129 3. Adds the output file to the ``coverage`` target's dependency list.
130
131 Inputs:
132
133 ``NAME``
134 Test name included in lcov info file
135
136 ``SOURCE_DIR``
137 Directory of source files
138
139 ``BINARY_DIR``
140 Directory of the ``.gcda`` and ``.gcno`` files
141
142 ``OUTPUT_FILE``
143 Output lcov coverage info file
144
145 Global dependencies:
146
147 ``CPICKER_CACHE_PATH``
148 Root directory of the c-picker generated files
149
150
151#]===]
152function(coverage_generate)
153 set(_OPTIONS_ARGS)
154 set(_ONE_VALUE_ARGS NAME SOURCE_DIR BINARY_DIR OUTPUT_FILE)
155 set(_MULTI_VALUE_ARGS)
156 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
157
158 set(TEST_NAME ${_MY_PARAMS_NAME})
159 set(SOURCE_DIR ${_MY_PARAMS_SOURCE_DIR})
160 set(BINARY_DIR ${_MY_PARAMS_BINARY_DIR})
161 set(TEMP_FILE ${_MY_PARAMS_OUTPUT_FILE}_temp)
162 set(OUTPUT_FILE ${_MY_PARAMS_OUTPUT_FILE})
163
164 # Collecting information from .gcda and .gcno files into an lcov .info file
165 # Mapping c-picker generated files' coverage info to the original source lines
166 add_custom_command(
167 OUTPUT ${TEMP_FILE} ${OUTPUT_FILE}
168 COMMAND ${LCOV_COMMAND}
169 --capture
170 --test-name ${TEST_NAME}
171 --directory ${BINARY_DIR}
172 --base-directory ${SOURCE_DIR}
173 --output-file ${TEMP_FILE}
Imre Kis0e65dd62022-04-20 17:52:35 +0200174 --rc lcov_branch_coverage=1
Imre Kisa21712e2019-10-08 12:56:59 +0200175 COMMAND ${CPICKER_COVERAGE_MAPPER_COMMAND}
176 --input ${TEMP_FILE}
177 --output ${OUTPUT_FILE}
178 --mapping-path ${CPICKER_CACHE_PATH}
179 )
180
181 add_coverage_dependency(
182 TARGET coverage
183 DEPENDS ${OUTPUT_FILE}
184 )
185endfunction()
186
187#[===[.rst:
188.. cmake:command:: coverage_filter
189
190 .. code-block:: cmake
191
192 coverage_filter(
193 INPUT_FILE input_file
194 OUTPUT_FILE output_file
195 INCLUDE_DIRECTORY include_directory
196 )
197
198 The function filters the coverage data by including only the coverage of the
199 files of ``INCLUDE_DIRECTORY`` or its subdirectories. It adds the filtered
200 output file to the ``coverage`` target's dependency list.
201
202 Inputs:
203
204 ``INPUT_FILE``
205 Input lcov coverage info file
206
207 ``OUTPUT_FILE``
208 Output lcov coverage info file
209
210 ``INCLUDE_DIRECTORY``
211 Root directory of included files
212
213#]===]
214function(coverage_filter)
215 set(_OPTIONS_ARGS)
216 set(_ONE_VALUE_ARGS INPUT_FILE OUTPUT_FILE INCLUDE_DIRECTORY)
217 set(_MULTI_VALUE_ARGS)
218 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
219
220 set(INPUT_FILE ${_MY_PARAMS_INPUT_FILE})
221 set(OUTPUT_FILE ${_MY_PARAMS_OUTPUT_FILE})
222 set(INCLUDE_DIRECTORY ${_MY_PARAMS_INCLUDE_DIRECTORY})
223
224 # The pattern must be an absolute path ending with an asterisk
225 get_filename_component(INCLUDE_DIRECTORY_ABSPATH "${INCLUDE_DIRECTORY}" ABSOLUTE)
226 set(INCLUDE_DIRECTORY_ABSPATH "${INCLUDE_DIRECTORY_ABSPATH}/*")
227
228 add_custom_command(
229 OUTPUT ${OUTPUT_FILE}
230 COMMAND ${LCOV_COMMAND}
231 --extract ${INPUT_FILE} \"${INCLUDE_DIRECTORY_ABSPATH}\"
232 --output-file ${OUTPUT_FILE}
Imre Kis0e65dd62022-04-20 17:52:35 +0200233 --rc lcov_branch_coverage=1
Imre Kisa21712e2019-10-08 12:56:59 +0200234 DEPENDS ${INPUT_FILE}
235 )
236
237 add_coverage_dependency(
238 TARGET coverage
239 DEPENDS ${OUTPUT_FILE}
240 )
241endfunction()
242
243#[===[.rst:
244.. cmake:command:: coverage_generate_report
245
246 .. code-block:: cmake
247
248 coverage_generate_report(
249 INPUT_FILE input_file
250 OUTPUT_DIRECTORY output_directory
251 )
252
253 The function generates a HTML coverage report from the lcov info file into
254 the ``OUTPUT_DIRECTORY``. It adds the output directory to the
255 ``coverage_report`` target's dependency list.
256
257 Inputs:
258
259 ``INPUT_FILE``
260 Input lcov coverage info file
261
262 ``OUTPUT_DIRECTORY``
263 Output directory of the coverage report where the ``index.html`` is the
264 root file of the report.
265
266#]===]
267function(coverage_generate_report)
268 set(_OPTIONS_ARGS)
269 set(_ONE_VALUE_ARGS INPUT_FILE OUTPUT_DIRECTORY)
270 set(_MULTI_VALUE_ARGS)
271 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
272
273 set(INPUT_FILE ${_MY_PARAMS_INPUT_FILE})
274 set(OUTPUT_DIRECTORY ${_MY_PARAMS_OUTPUT_DIRECTORY})
275
276 add_custom_command(
277 OUTPUT ${OUTPUT_DIRECTORY}
278 COMMAND genhtml ${INPUT_FILE}
279 --show-details
Imre Kis0e65dd62022-04-20 17:52:35 +0200280 --branch-coverage
Imre Kisa21712e2019-10-08 12:56:59 +0200281 --output-directory ${OUTPUT_DIRECTORY}
282 DEPENDS ${INPUT_FILE}
283 )
284
285 add_coverage_dependency(
286 TARGET coverage_report
287 DEPENDS ${OUTPUT_DIRECTORY}
288 )
289endfunction()