7575# - Add -fprofile-abs-path to make gcno files contain absolute paths
7676# - Fix BASE_DIRECTORY not working when defined
7777# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines
78+ #
79+ # 2021-05-10, Martin Stump
80+ # - Check if the generator is multi-config before warning about non-Debug builds
81+ #
82+ # 2022-02-22, Marko Wehle
83+ # - Change gcovr output from -o <filename> for --xml <filename> and --html <filename> output respectively.
84+ # This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt".
85+ #
86+ # 2022-09-28, Sebastian Mueller
87+ # - fix append_coverage_compiler_flags_to_target to correctly add flags
88+ # - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent)
89+ #
7890# USAGE:
7991#
8092# 1. Copy this file into your cmake modules path.
8395# using a CMake option() to enable it just optionally):
8496# include(CodeCoverage)
8597#
86- # 3. Append necessary compiler flags:
98+ # 3. Append necessary compiler flags for all supported source files :
8799# append_coverage_compiler_flags()
100+ # Or for specific target:
101+ # append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME)
88102#
89103# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og
90104#
@@ -137,30 +151,34 @@ if(NOT GCOV_PATH)
137151 message (FATAL_ERROR "gcov not found! Aborting..." )
138152endif () # NOT GCOV_PATH
139153
154+ # Check supported compiler (Clang, GNU and Flang)
140155get_property (LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES )
141- list (GET LANGUAGES 0 LANG)
142-
143- if ("${CMAKE_${LANG} _COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" )
156+ foreach (LANG ${LANGUAGES} )
157+ if ("${CMAKE_${LANG} _COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" )
144158 if ("${CMAKE_${LANG} _COMPILER_VERSION}" VERSION_LESS 3)
145- message (FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting..." )
146- endif ()
147- elseif (NOT CMAKE_COMPILER_IS_GNUCXX)
148- if ("${CMAKE_Fortran_COMPILER_ID} " MATCHES "[Ff]lang" )
149- # Do nothing; exit conditional without error if true
150- elseif ("${CMAKE_Fortran_COMPILER_ID} " MATCHES "GNU" )
151- # Do nothing; exit conditional without error if true
152- else ()
153- message (FATAL_ERROR "Compiler is not GNU gcc! Aborting..." )
159+ message (FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting..." )
154160 endif ()
155- endif ()
161+ elseif (NOT "${CMAKE_${LANG} _COMPILER_ID}" MATCHES "GNU"
162+ AND NOT "${CMAKE_${LANG} _COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang" )
163+ message (FATAL_ERROR "Compiler is not GNU or Flang! Aborting..." )
164+ endif ()
165+ endforeach ()
156166
157- set (COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest -coverage"
167+ set (COVERAGE_COMPILER_FLAGS "-g --coverage"
158168 CACHE INTERNAL "" )
169+
159170if (CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)" )
160171 include (CheckCXXCompilerFlag)
161- check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path)
162- if (HAVE_fprofile_abs_path)
163- set (COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path" )
172+ check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path)
173+ if (HAVE_cxx_fprofile_abs_path)
174+ set (COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path" )
175+ endif ()
176+ endif ()
177+ if (CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)" )
178+ include (CheckCCompilerFlag)
179+ check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path)
180+ if (HAVE_c_fprofile_abs_path)
181+ set (COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path" )
164182 endif ()
165183endif ()
166184
@@ -191,9 +209,10 @@ mark_as_advanced(
191209 CMAKE_EXE_LINKER_FLAGS_COVERAGE
192210 CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
193211
194- if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" )
212+ get_property (GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
213+ if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG))
195214 message (WARNING "Code coverage results with an optimised (non-Debug) build may be misleading" )
196- endif () # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
215+ endif () # NOT ( CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)
197216
198217if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU" )
199218 link_libraries (gcov)
@@ -217,7 +236,7 @@ endif()
217236# )
218237function (setup_target_for_coverage_lcov)
219238
220- set (options NO_DEMANGLE)
239+ set (options NO_DEMANGLE SONARQUBE )
221240 set (oneValueArgs BASE_DIRECTORY NAME )
222241 set (multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS)
223242 cmake_parse_arguments (Coverage "${options} " "${oneValueArgs} " "${multiValueArgs} " ${ARGN} )
@@ -287,6 +306,18 @@ function(setup_target_for_coverage_lcov)
287306 ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o
288307 ${Coverage_NAME} ${Coverage_NAME} .info
289308 )
309+ if (${Coverage_SONARQUBE} )
310+ # Generate SonarQube output
311+ set (GCOVR_XML_CMD
312+ ${GCOVR_PATH} --sonarqube ${Coverage_NAME} _sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
313+ ${GCOVR_EXCLUDE_ARGS} --object-directory =${PROJECT_BINARY_DIR}
314+ )
315+ set (GCOVR_XML_CMD_COMMAND
316+ COMMAND ${GCOVR_XML_CMD}
317+ )
318+ set (GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME} _sonarqube.xml)
319+ set (GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME} _sonarqube.xml." )
320+ endif ()
290321
291322
292323 if (CODE_COVERAGE_VERBOSE)
@@ -318,6 +349,12 @@ function(setup_target_for_coverage_lcov)
318349 message (STATUS "Command to generate lcov HTML output: " )
319350 string (REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD} " )
320351 message (STATUS "${LCOV_GEN_HTML_CMD_SPACED} " )
352+
353+ if (${Coverage_SONARQUBE} )
354+ message (STATUS "Command to generate SonarQube XML output: " )
355+ string (REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD} " )
356+ message (STATUS "${GCOVR_XML_CMD_SPACED} " )
357+ endif ()
321358 endif ()
322359
323360 # Setup target
@@ -329,13 +366,15 @@ function(setup_target_for_coverage_lcov)
329366 COMMAND ${LCOV_BASELINE_COUNT_CMD}
330367 COMMAND ${LCOV_FILTER_CMD}
331368 COMMAND ${LCOV_GEN_HTML_CMD}
369+ ${GCOVR_XML_CMD_COMMAND}
332370
333371 # Set output files as GENERATED (will be removed on 'make clean')
334372 BYPRODUCTS
335373 ${Coverage_NAME} .base
336374 ${Coverage_NAME} .capture
337375 ${Coverage_NAME} .total
338376 ${Coverage_NAME} .info
377+ ${GCOVR_XML_CMD_BYPRODUCTS}
339378 ${Coverage_NAME} /index.html
340379 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
341380 DEPENDS ${Coverage_DEPENDENCIES}
@@ -347,6 +386,7 @@ function(setup_target_for_coverage_lcov)
347386 add_custom_command (TARGET ${Coverage_NAME} POST_BUILD
348387 COMMAND ;
349388 COMMENT "Lcov code coverage info report saved in ${Coverage_NAME} .info."
389+ ${GCOVR_XML_CMD_COMMENT}
350390 )
351391
352392 # Show info where to find the report
@@ -415,8 +455,8 @@ function(setup_target_for_coverage_gcovr_xml)
415455 )
416456 # Running gcovr
417457 set (GCOVR_XML_CMD
418- ${GCOVR_PATH} --xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS }
419- --object-directory =${PROJECT_BINARY_DIR} -o ${Coverage_NAME} .xml
458+ ${GCOVR_PATH} --xml ${Coverage_NAME} .xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
459+ ${GCOVR_EXCLUDE_ARGS} --object-directory =${PROJECT_BINARY_DIR}
420460 )
421461
422462 if (CODE_COVERAGE_VERBOSE)
@@ -511,9 +551,8 @@ function(setup_target_for_coverage_gcovr_html)
511551 )
512552 # Running gcovr
513553 set (GCOVR_HTML_CMD
514- ${GCOVR_PATH} --html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
554+ ${GCOVR_PATH} --html ${Coverage_NAME} /index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
515555 ${GCOVR_EXCLUDE_ARGS} --object-directory =${PROJECT_BINARY_DIR}
516- -o ${Coverage_NAME} /index.html
517556 )
518557
519558 if (CODE_COVERAGE_VERBOSE)
@@ -567,19 +606,20 @@ endfunction() # setup_target_for_coverage_gcovr_html
567606# NO_DEMANGLE # Don't demangle C++ symbols
568607# # even if c++filt is found
569608# SKIP_HTML # Don't create html report
609+ # POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths
570610# )
571611function (setup_target_for_coverage_fastcov)
572612
573613 set (options NO_DEMANGLE SKIP_HTML)
574614 set (oneValueArgs BASE_DIRECTORY NAME )
575- set (multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS)
615+ set (multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD )
576616 cmake_parse_arguments (Coverage "${options} " "${oneValueArgs} " "${multiValueArgs} " ${ARGN} )
577617
578618 if (NOT FASTCOV_PATH)
579619 message (FATAL_ERROR "fastcov not found! Aborting..." )
580620 endif ()
581621
582- if (NOT GENHTML_PATH)
622+ if (NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH)
583623 message (FATAL_ERROR "genhtml not found! Aborting..." )
584624 endif ()
585625
@@ -608,12 +648,14 @@ function(setup_target_for_coverage_fastcov)
608648 set (FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
609649 --search-directory ${BASEDIR}
610650 --process-gcno
611- --lcov
612- --output ${Coverage_NAME} .info
613- --exclude ${FASTCOV_EXCLUDES}
651+ --output ${Coverage_NAME} .json
614652 --exclude ${FASTCOV_EXCLUDES}
615653 )
616654
655+ set (FASTCOV_CONVERT_CMD ${FASTCOV_PATH}
656+ -C ${Coverage_NAME} .json --lcov --output ${Coverage_NAME} .info
657+ )
658+
617659 if (Coverage_SKIP_HTML)
618660 set (FASTCOV_HTML_CMD ";" )
619661 else ()
@@ -622,6 +664,11 @@ function(setup_target_for_coverage_fastcov)
622664 )
623665 endif ()
624666
667+ set (FASTCOV_POST_CMD ";" )
668+ if (Coverage_POST_CMD)
669+ set (FASTCOV_POST_CMD ${Coverage_POST_CMD} )
670+ endif ()
671+
625672 if (CODE_COVERAGE_VERBOSE)
626673 message (STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):" )
627674
@@ -633,11 +680,20 @@ function(setup_target_for_coverage_fastcov)
633680 string (REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD} " )
634681 message (" ${FASTCOV_CAPTURE_CMD_SPACED} " )
635682
683+ message (" Converting fastcov .json to lcov .info:" )
684+ string (REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD} " )
685+ message (" ${FASTCOV_CONVERT_CMD_SPACED} " )
686+
636687 if (NOT Coverage_SKIP_HTML)
637688 message (" Generating HTML report: " )
638689 string (REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD} " )
639690 message (" ${FASTCOV_HTML_CMD_SPACED} " )
640691 endif ()
692+ if (Coverage_POST_CMD)
693+ message (" Running post command: " )
694+ string (REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD} " )
695+ message (" ${FASTCOV_POST_CMD_SPACED} " )
696+ endif ()
641697 endif ()
642698
643699 # Setup target
@@ -650,11 +706,14 @@ function(setup_target_for_coverage_fastcov)
650706
651707 COMMAND ${FASTCOV_EXEC_TESTS_CMD}
652708 COMMAND ${FASTCOV_CAPTURE_CMD}
709+ COMMAND ${FASTCOV_CONVERT_CMD}
653710 COMMAND ${FASTCOV_HTML_CMD}
711+ COMMAND ${FASTCOV_POST_CMD}
654712
655713 # Set output files as GENERATED (will be removed on 'make clean')
656714 BYPRODUCTS
657715 ${Coverage_NAME} .info
716+ ${Coverage_NAME} .json
658717 ${Coverage_NAME} /index.html # report directory
659718
660719 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
@@ -663,7 +722,7 @@ function(setup_target_for_coverage_fastcov)
663722 COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report."
664723 )
665724
666- set (INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME} .info." )
725+ set (INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME} .info and ${Coverage_NAME} .json ." )
667726 if (NOT Coverage_SKIP_HTML)
668727 string (APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR} /${Coverage_NAME} /index.html in your browser to view the coverage report." )
669728 endif ()
@@ -680,3 +739,12 @@ function(append_coverage_compiler_flags)
680739 set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS} " PARENT_SCOPE)
681740 message (STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS} " )
682741endfunction () # append_coverage_compiler_flags
742+
743+ # Setup coverage for specific library
744+ function (append_coverage_compiler_flags_to_target name )
745+ separate_arguments (_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS} " )
746+ target_compile_options (${name} PRIVATE ${_flag_list} )
747+ if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU" )
748+ target_link_libraries (${name} PRIVATE gcov)
749+ endif ()
750+ endfunction ()
0 commit comments