OpenJij/OpenJij

View on GitHub
cmake/Findcodecov.cmake

Summary

Maintainability
Test Coverage
# This file is part of CMake-codecov.
#
# Copyright (c)
#   2015-2020 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"
)


# 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 required 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})
    if (NOT ${LANG} MATCHES "^(C|CXX|Fortran)$")
        message(STATUS "Skipping coverage for unsupported language: ${LANG}")
        continue()
    endif ()

    # Coverage flags are not dependent 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()
            else ()
                message(WARNING "Code coverage is not available for ${COMPILER}"
                        " compiler. Targets using this compiler will be "
                        "compiled without it.")
            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)
    # Usually, only the last extension of the file should be checked, to avoid
    # template files (i.e. *.t.cpp) are checked with the full file extension.
    # However, this feature requires CMake 3.14 or later.
    set(EXT_COMP "LAST_EXT")
    if(${CMAKE_VERSION} VERSION_LESS "3.14.0")
        set(EXT_COMP "EXT")
    endif()

    get_filename_component(FILE_EXT "${FILE}" ${EXT_COMP})
    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(WARNING "Can't use code coverage for target ${TNAME}, because "
                "it will be compiled by incompatible compilers. Target will be "
                "compiled without code coverage.")
        return()

    elseif (NUM_COMPILERS EQUAL 0)
        message(WARNING "Can't use code coverage for target ${TNAME}, because "
                "it uses an unknown compiler. Target will be compiled without "
                "code coverage.")
        return()

    elseif (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS")
        # A warning has been printed before, so just return if flags for this
        # compiler aren't available.
        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()

    if(${CMAKE_VERSION} VERSION_LESS "3.15.0")
        set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
            "${CLEAN_FILES}")
    else()
        set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES
            "${CLEAN_FILES}")
    endif()


    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)