1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-02 00:42:33 +01:00
openrw/cmake/modules/CodeCoverage.cmake
Anonymous Maarten c3573c8070 cmake: update code coverage cmake script
- add branch coverage
- ignore interface libraries
- ignore generator expressions
2018-12-28 00:58:10 +01:00

476 lines
18 KiB
CMake

# DOES NOT SUPPORT RUNNING IN PARALLEL: https://github.com/linux-test-project/lcov/issues/37
# TODO: macos xcode coverage: works? What dependencies?
get_filename_component(_CODECOVERAGE_MODDIR ${CMAKE_CURRENT_LIST_FILE} PATH)
function(codecoverage_enable)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_program(GCOV_BIN gcov)
if(NOT GCOV_BIN)
message(FATAL_ERROR "gcov not found")
endif()
set(COV_BIN "${GCOV_BIN}")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
find_program(LLVM_COV_BIN llvm-cov)
if(NOT LLVM_COV_BIN)
message(FATAL_ERROR "llvm-cov not found")
endif()
configure_file("${_CODECOVERAGE_MODDIR}/llvm-cov-wrapper.in" "${CMAKE_BINARY_DIR}/llvm-cov-wrapper")
set(COV_BIN "${CMAKE_BINARY_DIR}/llvm-cov-wrapper")
else()
message(FATAL_ERROR "Code coverage unsupported for this compiler.")
endif()
set(COV_BIN "${COV_BIN}" CACHE INTERNAL "Coverage tool")
find_program(LCOV_BIN lcov)
if(NOT LCOV_BIN)
message(FATAL_ERROR "lcov not found")
endif()
find_program(GENINFO_BIN geninfo)
if(NOT GENINFO_BIN)
message(FATAL_ERROR "geninfo not found")
endif()
find_program(GENHTML_BIN genhtml)
if(NOT GENHTML_BIN)
message(FATAL_ERROR "genhtml not found")
endif()
find_program(CPPFILT_BIN c++filt)
if(NOT CPPFILT_BIN)
message(FATAL_ERROR "c++filt not found")
endif()
message(STATUS "TEST_COVERAGE enabled. Optimizations are disabled, debug data will be added to targets.")
# Remove global optimization flags
foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES} "")
foreach(LANGUAGE C CXX)
set(FLAGVARNAME "CMAKE_${LANGUAGE}_FLAGS")
if(CONFIG_TYPE)
string(TOUPPER "${CONFIG_TYPE}" CONFIG_TYPE)
set(FLAGVARNAME "${FLAGVARNAME}_${CONFIG_TYPE}")
endif()
string(REGEX REPLACE "-O[s0-3]" "" _OUT "${${FLAGVARNAME}}")
set("${FLAGVARNAME}" "${_OUT}" PARENT_SCOPE)
endforeach()
endforeach()
set(COVERAGE_COMPILE_FLAGS "-O0 -fprofile-arcs -ftest-coverage -g" CACHE STRING "Compile flags for compiling with coverage support")
set(COVERAGE_LINK_FLAGS "-O0 -fprofile-arcs -ftest-coverage" CACHE STRING "Link flags for linking with coverage support")
set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data")
set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init" CACHE PATH "Where to put initial coverage")
set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture" CACHE PATH "Where to put final coverage")
set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html" CACHE PATH "Where to put html coverage reports")
file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT})
file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE})
add_custom_target(gcov)
add_custom_target(lcov-capture-init)
add_custom_target(lcov-capture)
define_property(GLOBAL
PROPERTY COVERAGE_TARGETS
BRIEF_DOCS "List of all coverage targets"
FULL_DOCS "All targets that is collected coverage over"
)
define_property(GLOBAL
PROPERTY LCOV_CAPTURE_FILES
BRIEF_DOCS "List of all capture files"
FULL_DOCS "All capture files which have coverage data included"
)
define_property(GLOBAL
PROPERTY LCOV_CAPTURE_INIT_FILES
BRIEF_DOCS "List of all init capture files"
FULL_DOCS "Empty capture files for the baseline coverage"
)
endfunction()
function(_coverage_path_dest _RETURNVAR PATH)
string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" PATH "${PATH}")
if(IS_ABSOLUTE "${PATH}")
file(RELATIVE_PATH PATH "${CMAKE_CURRENT_SOURCE_DIR}" ${PATH})
endif()
string(REPLACE ".." "__" PATH "${PATH}")
set("${_RETURNVAR}" "${PATH}" PARENT_SCOPE)
endfunction()
function(_coverage_filter_languages _RETURNVAR)
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
set(FILTERED_FILES)
foreach(FILE ${ARGN})
if(FILE MATCHES "^\\$<")
#ignore generator expressions
continue()
endif()
get_filename_component(FILE_EXT "${FILE}" EXT)
string(TOLOWER "${FILE_EXT}" FILE_EXT)
string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
foreach(LANG ${ENABLED_LANGUAGES})
if(FILE_EXT IN_LIST CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS)
list(APPEND FILTERED_FILES "${FILE}")
endif()
endforeach()
endforeach()
set("${_RETURNVAR}" "${FILTERED_FILES}" PARENT_SCOPE)
endfunction()
function(coverage_add_target _TARGET)
cmake_parse_arguments(CAT "" "" "EXCEPT" ${ARGN})
get_target_property(TARGET_TYPE "${_TARGET}" TYPE)
if(TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
return()
endif()
get_property(COMPILE_FLAGS TARGET "${_TARGET}" PROPERTY COMPILE_FLAGS)
set_property(TARGET "${_TARGET}"
APPEND_STRING
PROPERTY COMPILE_FLAGS " ${COVERAGE_COMPILE_FLAGS}"
)
set_property(TARGET "${_TARGET}"
APPEND_STRING
PROPERTY LINK_FLAGS " ${COVERAGE_LINK_FLAGS}"
)
get_target_property(SOURCES "${_TARGET}" SOURCES)
set(COVERAGE_FILES)
foreach(SOURCE ${SOURCES})
if("${SOURCE}" IN_LIST CAT_EXCEPT)
continue()
endif()
_coverage_path_dest(RELSOURCE "${SOURCE}")
list(APPEND COVERAGE_FILES
"CMakeFiles/${_TARGET}.dir/${RELSOURCE}.gcno"
"CMakeFiles/${_TARGET}.dir/${RELSOURCE}.gcda"
)
endforeach()
set_property(GLOBAL
APPEND PROPERTY COVERAGE_TARGETS
"${_TARGET}"
)
set_property(DIRECTORY
APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES
${COVERAGE_FILES}
)
coverage_gcov_target(${_TARGET} EXCEPT "${CAT_EXCEPT}")
coverage_lcov_target(${_TARGET} EXCEPT "${CAT_EXCEPT}")
endfunction()
function(coverage_collect)
codecoverage_lcov_capture_initial()
codecoverage_lcov_capture()
endfunction()
function(coverage_gcov_target _TARGET)
cmake_parse_arguments(CGT "" "" "EXCEPT" ${ARGN})
set(GCOV_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_TARGET}.dir")
get_target_property(SOURCES "${_TARGET}" SOURCES)
_coverage_filter_languages(SOURCES ${SOURCES})
foreach(SOURCE ${SOURCES})
_coverage_path_dest(RELSOURCE "${SOURCE}")
if("${SOURCE}" IN_LIST CGT_EXCEPT)
continue()
endif()
get_filename_component(RELSOURCE_PATH "${RELSOURCE}" PATH)
add_custom_command(OUTPUT "${GCOV_DIR}/${RELSOURCE}.gcov"
COMMAND test -s "${GCOV_DIR}/${RELSOURCE}.gcda"
&& "${COV_BIN}" "${GCOV_DIR}/${RELSOURCE}.gcno" > /dev/null
|| true
DEPENDS "${_TARGET}" "${GCOV_DIR}/${RELSOURCE}.gcno" "${GCOV_DIR}/${RELSOURCE}.gcda"
WORKING_DIRECTORY "${GCOV_DIR}/${RELSOURCE_PATH}"
COMMENT "Capturing gcov data for source ${SOURCE} of target ${_TARGET}"
)
list(APPEND GCOV_FILES "${GCOV_DIR}/${RELSOURCE}.gcov")
endforeach()
add_custom_target("${_TARGET}-gcov"
DEPENDS ${GCOV_FILES}
COMMENT "Capturing gcov data of target ${_TARGET}"
)
add_dependencies(gcov "${_TARGET}-gcov")
endfunction()
function(coverage_lcov_target _TARGET)
coverage_lcov_target_initial("${_TARGET}" ${ARGN})
coverage_lcov_capture_target("${_TARGET}" ${ARGN})
endfunction()
function(coverage_lcov_target_initial _TARGET)
cmake_parse_arguments(CLTI "" "" "EXCEPT" ${ARGN})
set(LCOV_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_TARGET}.dir")
set(INIT_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_TARGET}-capture-init.dir")
get_target_property(SOURCES "${_TARGET}" SOURCES)
_coverage_filter_languages(SOURCES ${SOURCES})
set(GENINFO_FILES)
foreach(SOURCE ${SOURCES})
if("${SOURCE}" IN_LIST CLTI_EXCEPT)
continue()
endif()
# generate empty coverage files
_coverage_path_dest(RELSOURCE "${SOURCE}")
get_filename_component(RELSOURCE_DIR "${RELSOURCE}" DIRECTORY)
file(MAKE_DIRECTORY "${INIT_DIR}/${RELSOURCE_DIR}")
set(GENINFO_FILE "${INIT_DIR}/${RELSOURCE}.info.init")
list(APPEND GENINFO_FILES "${GENINFO_FILE}")
add_custom_command(OUTPUT "${GENINFO_FILE}"
COMMAND "${GENINFO_BIN}"
--quiet --base-directory "${PROJECT_SOURCE_DIR}" --initial
--gcov-tool "${COV_BIN}" --output-filename "${GENINFO_FILE}"
--no-external "${LCOV_DIR}/${RELSOURCE}.gcno"
DEPENDS "${_TARGET}"
COMMENT "Capturing initial coverage data for ${SOURCE}"
)
endforeach()
set(OUTFILE "${LCOV_DATA_PATH_INIT}/${_TARGET}.info")
coverage_lcov_merge_files(
OUTFILE "${OUTFILE}"
FILES ${GENINFO_FILES}
FLAGS "--initial"
REMOVE_PATTERNS
)
add_custom_target("${_TARGET}-capture-init"
DEPENDS "${OUTFILE}"
COMMENT "Capturing initial coverage data for target ${_TARGET}"
)
add_dependencies(lcov-capture-init "${_TARGET}-capture-init")
set_property(GLOBAL APPEND PROPERTY LCOV_CAPTURE_INIT_FILES "${OUTFILE}")
endfunction()
function(coverage_lcov_capture_target _TARGET)
cmake_parse_arguments(CLC "" "" "EXCEPT" ${ARGN})
set(LCOV_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_TARGET}.dir")
set(CAP_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_TARGET}-geninfo.dir")
set(INIT_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_TARGET}-capture-init.dir")
get_target_property(SOURCES "${_TARGET}" SOURCES)
_coverage_filter_languages(SOURCES ${SOURCES})
set(GCDA_FILES)
set(GENINFO_FILES)
foreach(SOURCE ${SOURCES})
if("${SOURCE}" IN_LIST CLC_EXCEPT)
continue()
endif()
_coverage_path_dest(RELSOURCE "${SOURCE}")
get_filename_component(RELSOURCE_DIR "${RELSOURCE}" DIRECTORY)
file(MAKE_DIRECTORY "${CAP_DIR}/${RELSOURCE_DIR}")
set(OUTFILE "${CAP_DIR}/${RELSOURCE}.info")
list(APPEND GENINFO_FILES "${OUTFILE}")
list(APPEND GCDA_FILES "${LCOV_DIR}/${RELSOURCE}.gcda")
add_custom_command(OUTPUT "${LCOV_DIR}/${RELSOURCE}.gcda"
COMMAND "${CMAKE_COMMAND}" -E touch "${LCOV_DIR}/${RELSOURCE}.gcda"
DEPENDS "${_TARGET}"
COMMENT "Touching ${LCOV_DIR}/${RELSOURCE}.gcda"
)
add_custom_command(OUTPUT "${OUTFILE}"
COMMAND test -s "${LCOV_DIR}/${RELSOURCE}.gcda"
&& "${GENINFO_BIN}" --quiet --base-directory
"${PROJECT_SOURCE_DIR}" --gcov-tool "${COV_BIN}"
--output-filename "${OUTFILE}" --no-external
--rc lcov_branch_coverage=1
"${LCOV_DIR}/${RELSOURCE}.gcda"
|| cp "${INIT_DIR}/${RELSOURCE}.info.init" "${OUTFILE}"
DEPENDS "${_TARGET}" "${_TARGET}-capture-init" "${LCOV_DIR}/${RELSOURCE}.gcda"
COMMENT "Capturing coverage data for ${SOURCE}"
)
endforeach()
# Concatenate all files generated by geninfo to a single file per target.
set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${_TARGET}.info")
coverage_lcov_merge_files(OUTFILE "${OUTFILE}"
FILES ${GENINFO_FILES}
)
add_custom_target("${_TARGET}-geninfo"
DEPENDS "${OUTFILE}"
COMMENT "Capturing coverage data for target ${_TARGET}"
)
add_custom_target("${_TARGET}-geninfo-clean"
COMMAND "${CMAKE_COMMAND}" -E remove "${OUTFILE}" ${GCDA_FILES}
COMMENT "Cleaning coverage data of target ${_TARGET}"
)
add_dependencies(lcov-capture "${_TARGET}-geninfo")
set_property(GLOBAL APPEND PROPERTY LCOV_CAPTURE_FILES "${OUTFILE}")
# Add target for generating html output for this target only.
file(MAKE_DIRECTORY "${LCOV_HTML_PATH}/${_TARGET}")
add_custom_target("${_TARGET}-lcov"
COMMAND "${GENHTML_BIN}" --quiet --sort --prefix "${PROJECT_SOURCE_DIR}"
--baseline-file "${LCOV_DATA_PATH_INIT}/${_TARGET}.info"
--output-directory "${LCOV_HTML_PATH}/${_TARGET}"
--highlight --legend --show-details --branch-coverage --function-coverage
--title "${PROJECT_NAME}" --target "${_TARGET}"
--demangle-cpp "${OUTFILE}"
DEPENDS "${_TARGET}-geninfo" "${_TARGET}-capture-init"
COMMENT "Generating coverage report for target ${_TARGET}"
)
endfunction()
function(codecoverage_lcov_capture_initial)
set(INITCAPTURE_FILE "${LCOV_DATA_PATH_INIT}/all_targets.info")
set(INITCAPTURE_FILE_UPLOAD "${INITCAPTURE_FILE}.upload")
get_property(LCOV_CAPTURE_INIT_FILES GLOBAL PROPERTY LCOV_CAPTURE_INIT_FILES)
coverage_lcov_merge_files(OUTFILE "${INITCAPTURE_FILE}" FILES ${LCOV_CAPTURE_INIT_FILES})
add_custom_target(lcov-geninfo-init
DEPENDS ${INITCAPTURE_FILE} lcov-capture-init
COMMENT "Capturing global initial coverage data"
)
string(REPLACE "/" "\\/" PROJBINDIR_ESCAPED "${PROJECT_BINARY_DIR}/")
string(REPLACE "/" "\\/" PROJSRCDIR_ESCAPED "${PROJECT_SOURCE_DIR}/")
add_custom_command(OUTPUT "${INITCAPTURE_FILE_UPLOAD}"
COMMAND sed -e 's/${PROJBINDIR_ESCAPED}//g' -e 's/${PROJSRCDIR_ESCAPED}//g' "${INITCAPTURE_FILE}" > "${INITCAPTURE_FILE_UPLOAD}"
DEPENDS lcov-geninfo-init "${INITCAPTURE_FILE}"
COMMENT "Creating ${INITCAPTURE_FILE_UPLOAD}"
)
add_custom_target(lcov-geninfo-init-upload
DEPENDS "${INITCAPTURE_FILE_UPLOAD}"
COMMENT "Preparing ${INITCAPTURE_FILE_UPLOAD} for upload"
)
endfunction()
function(codecoverage_lcov_capture)
set(TOTALCAPTURE_FILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info")
set(TOTALCAPTURE_FILE_UPLOAD "${TOTALCAPTURE_FILE}.upload")
get_property(COVERAGE_TARGETS GLOBAL PROPERTY COVERAGE_TARGETS)
get_property(LCOV_CAPTURE_FILES GLOBAL PROPERTY LCOV_CAPTURE_FILES)
set(GENINFO_CLEAN_TARGETS)
set(GENINFO_TARGETS)
foreach(COVERAGE_TARGET ${COVERAGE_TARGETS})
list(APPEND GENINFO_TARGETS "${COVERAGE_TARGET}-geninfo")
list(APPEND GENINFO_CLEAN_TARGETS "${COVERAGE_TARGET}-geninfo-clean")
endforeach()
coverage_lcov_merge_files(OUTFILE "${TOTALCAPTURE_FILE}" FILES ${LCOV_CAPTURE_FILES})
add_custom_target(lcov-geninfo
DEPENDS ${GENINFO_TARGETS} "${TOTALCAPTURE_FILE}"
COMMENT "Capturing global coverage data"
)
string(REPLACE "/" "\\/" PROJBINDIR_ESCAPED "${PROJECT_BINARY_DIR}/")
string(REPLACE "/" "\\/" PROJSRCDIR_ESCAPED "${PROJECT_SOURCE_DIR}/")
add_custom_command(OUTPUT "${TOTALCAPTURE_FILE_UPLOAD}"
COMMAND sed -e 's/^SF:${PROJBINDIR_ESCAPED}/SF:/g' -e 's/^SF:${PROJSRCDIR_ESCAPED}/SF:/g' "${TOTALCAPTURE_FILE}" > "${TOTALCAPTURE_FILE_UPLOAD}"
DEPENDS lcov-geninfo "${TOTALCAPTURE_FILE}"
COMMENT "Creating ${TOTALCAPTURE_FILE_UPLOAD}"
)
add_custom_target(lcov-geninfo-upload
DEPENDS "${TOTALCAPTURE_FILE_UPLOAD}"
COMMENT "Preparing ${TOTALCAPTURE_FILE_UPLOAD} for upload"
)
add_custom_target(lcov-geninfo-clean
COMMAND "${CMAKE_COMMAND}" -E remove "${TOTALCAPTURE_FILE}" "${TOTALCAPTURE_FILE_UPLOAD}"
COMMENT "Cleaning global coverage data"
DEPENDS ${GENINFO_CLEAN_TARGETS}
)
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"
--highlight --legend --show-details --branch-coverage --function-coverage
--title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}"
--demangle-cpp "${TOTALCAPTURE_FILE}"
DEPENDS lcov-geninfo-init lcov-geninfo
COMMENT "Generating global coverage report"
)
endfunction()
function(coverage_lcov_merge_files)
cmake_parse_arguments(CLMF "" "OUTFILE" "FILES;FLAGS;REMOVE_PATTERNS" ${ARGN})
set(OUTFILE "${CLMF_OUTFILE}")
# Generate merged file.
string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}")
add_custom_command(OUTPUT "${OUTFILE}.raw"
COMMAND cat ${CLMF_FILES} > ${OUTFILE}.raw
DEPENDS ${CLMF_FILES}
COMMENT "Generating ${FILE_REL}"
)
add_custom_command(OUTPUT "${OUTFILE}"
COMMAND "${LCOV_BIN}" --quiet -a "${OUTFILE}.raw" --output-file "${OUTFILE}"
--rc lcov_branch_coverage=1
--base-directory "${PROJECT_SOURCE_DIR}" ${CLMF_FLAGS}
COMMAND "${LCOV_BIN}" --quiet -r "${OUTFILE}" ${CLMF_REMOVE_PATTERNS}
--rc lcov_branch_coverage=1
--output-file "${OUTFILE}" ${CLMF_FLAGS}
DEPENDS "${OUTFILE}.raw"
COMMENT "Post-processing ${FILE_REL}"
)
endfunction()
function(add_coverage_upload_target_codecov_io)
cmake_parse_arguments(CU "" "GITSHA1;GITBRANCH;NAME" "" ${ARGN})
set(CAPTURE_FILES "${LCOV_DATA_PATH_INIT}/all_targets.info.upload" "${LCOV_DATA_PATH_CAPTURE}/all_targets.info.upload")
find_program(CURL_BIN curl)
if(NOT CURL_BIN)
message(FATAL_ERROR "curl not found")
endif()
find_program(BASH_BIN bash)
if(NOT BASH_BIN)
message(FATAL_ERROR "bash not found")
endif()
set(CODECOV_PATH "${PROJECT_BINARY_DIR}/codecov.sh")
set(CODECOV_SCRIPT_URL "https://codecov.io/bash" CACHE STRING "codecov.io bash script url")
set(CODECOV_FLAGS "" CACHE STRING "codecov.io flags")
string(REPLACE ";" "," CODECOV_FLAGS_ARG "${CODECOV_FLAGS}")
add_custom_command(OUTPUT "${CODECOV_PATH}"
COMMAND "${CURL_BIN}" "${CODECOV_SCRIPT_URL}" --insecure --output "${CODECOV_PATH}"
COMMENT "Fetching codecov.io bash script"
)
set(EXTRA_ARGS)
if(CU_GITSHA1)
list(APPEND EXTRA_ARGS -C "${CU_GITSHA1}")
endif()
if(CU_GITBRANCH)
list(APPEND EXTRA_ARGS -B "${CU_GITBRANCH}")
endif()
if(CU_NAME)
list(APPEND EXTRA_ARGS -n "${CU_NAME}")
endif()
foreach(CAPTURE_FILE ${CAPTURE_FILES})
list(APPEND EXTRA_ARGS -f "${CAPTURE_FILE}")
endforeach()
if(CODECOV_FLAG)
list(APPEND EXTRA_ARGS -F "${CODECOV_FLAGS_ARG}")
endif()
add_custom_target(coverage_upload
COMMAND "${BASH_BIN}" "${CODECOV_PATH}" ${EXTRA_ARGS}
-Z -A "--insecure" -U "--insecure"
-X gcov -X coveragepy -X search -X fix
DEPENDS "${CODECOV_PATH}" lcov-geninfo-upload lcov-geninfo-init-upload
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Uploading coverage to codecov.io"
)
endfunction()