# This file is part of CMake-codecov.
#
# Copyright (c)
#   2015-2017 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
#


# configuration
set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data")
set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init")
set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture")
set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html")




# Search for Gcov which is used by Lcov.
find_package(Gcov)


# This function will add lcov evaluation for target <TNAME>. Only sources of
# this target will be evaluated and no dependencies will be added. It will call
# geninfo on any source file of <TNAME> once and store the info file in the same
# directory.
#
# 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_lcov_target TNAME)
    if (LCOV_FOUND)
        # capture initial coverage data
        lcov_capture_initial_tgt(${TNAME})

        # capture coverage data after execution
        lcov_capture_tgt(${TNAME})
    endif ()
endfunction (add_lcov_target)




# include required Modules
include(FindPackageHandleStandardArgs)

# Search for required lcov binaries.
find_program(LCOV_BIN lcov)
find_program(GENINFO_BIN geninfo)
find_program(GENHTML_BIN genhtml)
find_package_handle_standard_args(lcov
        REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN
        )

# enable genhtml C++ demangeling, if c++filt is found.
set(GENHTML_CPPFILT_FLAG "")
find_program(CPPFILT_BIN c++filt)
if (NOT CPPFILT_BIN STREQUAL "")
    set(GENHTML_CPPFILT_FLAG "--demangle-cpp")
endif (NOT CPPFILT_BIN STREQUAL "")

# enable no-external flag for lcov, if available.
if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG)
    set(FLAG "")
    execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP)
    string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}")
    if (GENINFO_RES)
        set(FLAG "--no-external")
    endif ()

    set(GENINFO_EXTERN_FLAG "${FLAG}"
            CACHE STRING "Geninfo flag to exclude system sources.")
endif ()

# If Lcov was not found, exit module now.
if (NOT LCOV_FOUND)
    return()
endif (NOT LCOV_FOUND)




# Create directories to be used.
file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT})
file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE})

set(LCOV_REMOVE_PATTERNS "")

# This function will merge lcov files to a single target file. Additional lcov
# flags may be set with setting LCOV_EXTRA_FLAGS before calling this function.
function (lcov_merge_files OUTFILE ...)
    # Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files.
    list(REMOVE_AT ARGV 0)

    # Generate merged file.
    string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}")
    add_custom_command(OUTPUT "${OUTFILE}.raw"
            COMMAND cat ${ARGV} > ${OUTFILE}.raw
            DEPENDS ${ARGV}
            COMMENT "Generating ${FILE_REL}"
            )

    add_custom_command(OUTPUT "${OUTFILE}"
            COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE}
            --base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS}
            COMMAND ${LCOV_BIN} --quiet -r ${OUTFILE} ${LCOV_REMOVE_PATTERNS}
            --output-file ${OUTFILE} ${LCOV_EXTRA_FLAGS}
            DEPENDS ${OUTFILE}.raw
            COMMENT "Post-processing ${FILE_REL}"
            )
endfunction ()




# Add a new global target to generate initial coverage reports for all targets.
# This target will be used to generate the global initial info file, which is
# used to gather even empty report data.
if (NOT TARGET lcov-capture-init)
    add_custom_target(lcov-capture-init)
    set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "")
endif (NOT TARGET lcov-capture-init)


# This function will add initial capture of coverage data for target <TNAME>,
# which is needed to get also data for objects, which were not loaded at
# execution time. It will call geninfo for every source file of <TNAME> once and
# store the info file in the same directory.
function (lcov_capture_initial_tgt TNAME)
    # We don't have to check, if the target has support for coverage, thus this
    # will be checked by add_coverage_target in Findcoverage.cmake. Instead we
    # have to determine which gcov binary to use.
    get_target_property(TSOURCES ${TNAME} SOURCES)
    set(SOURCES "")
    set(TCOMPILER "")
    foreach (FILE ${TSOURCES})
        codecov_path_of_source(${FILE} FILE)
        if (NOT "${FILE}" STREQUAL "")
            codecov_lang_of_source(${FILE} LANG)
            if (NOT "${LANG}" STREQUAL "")
                list(APPEND SOURCES "${FILE}")
                set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
            endif ()
        endif ()
    endforeach ()

    # If no gcov binary was found, coverage data can't be evaluated.
    if (NOT GCOV_${TCOMPILER}_BIN)
        message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
        return()
    endif ()

    set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
    set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")


    set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
    set(GENINFO_FILES "")
    foreach(FILE ${SOURCES})
        # generate empty coverage files
        set(OUTFILE "${TDIR}/${FILE}.info.init")
        list(APPEND GENINFO_FILES ${OUTFILE})

        add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN}
                --quiet --base-directory ${PROJECT_SOURCE_DIR} --initial
                --gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE}
                ${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno
                DEPENDS ${TNAME}
                COMMENT "Capturing initial coverage data for ${FILE}"
                )
    endforeach()

    # Concatenate all files generated by geninfo to a single file per target.
    set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info")
    set(LCOV_EXTRA_FLAGS "--initial")
    lcov_merge_files("${OUTFILE}" ${GENINFO_FILES})
    add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE})

    # add geninfo file generation to global lcov-geninfo target
    add_dependencies(lcov-capture-init ${TNAME}-capture-init)
    set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}"
            "${OUTFILE}" CACHE INTERNAL ""
            )
endfunction (lcov_capture_initial_tgt)


# This function will generate the global info file for all targets. It has to be
# called after all other CMake functions in the root CMakeLists.txt file, to get
# a full list of all targets that generate coverage data.
function (lcov_capture_initial)
    # Skip this function (and do not create the following targets), if there are
    # no input files.
    if ("${LCOV_CAPTURE_INIT_FILES}" STREQUAL "")
        return()
    endif ()

    # Add a new target to merge the files of all targets.
    set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info")
    lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES})
    add_custom_target(lcov-geninfo-init ALL	DEPENDS ${OUTFILE}
            lcov-capture-init
            )
endfunction (lcov_capture_initial)




# Add a new global target to generate coverage reports for all targets. This
# target will be used to generate the global info file.
if (NOT TARGET lcov-capture)
    add_custom_target(lcov-capture)
    set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "")
endif (NOT TARGET lcov-capture)


# This function will add capture of coverage data for target <TNAME>, which is
# needed to get also data for objects, which were not loaded at execution time.
# It will call geninfo for every source file of <TNAME> once and store the info
# file in the same directory.
function (lcov_capture_tgt TNAME)
    # We don't have to check, if the target has support for coverage, thus this
    # will be checked by add_coverage_target in Findcoverage.cmake. Instead we
    # have to determine which gcov binary to use.
    get_target_property(TSOURCES ${TNAME} SOURCES)
    set(SOURCES "")
    set(TCOMPILER "")
    foreach (FILE ${TSOURCES})
        codecov_path_of_source(${FILE} FILE)
        if (NOT "${FILE}" STREQUAL "")
            codecov_lang_of_source(${FILE} LANG)
            if (NOT "${LANG}" STREQUAL "")
                list(APPEND SOURCES "${FILE}")
                set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID})
            endif ()
        endif ()
    endforeach ()

    # If no gcov binary was found, coverage data can't be evaluated.
    if (NOT GCOV_${TCOMPILER}_BIN)
        message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.")
        return()
    endif ()

    set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}")
    set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}")


    set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir)
    set(GENINFO_FILES "")
    foreach(FILE ${SOURCES})
        # Generate coverage files. If no .gcda file was generated during
        # execution, the empty coverage file will be used instead.
        set(OUTFILE "${TDIR}/${FILE}.info")
        list(APPEND GENINFO_FILES ${OUTFILE})

        add_custom_command(OUTPUT ${OUTFILE}
                COMMAND test -f "${TDIR}/${FILE}.gcda"
                && ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory
                ${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN}
                --output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG}
                ${TDIR}/${FILE}.gcda
                || cp ${OUTFILE}.init ${OUTFILE}
                DEPENDS ${TNAME} ${TNAME}-capture-init
                COMMENT "Capturing coverage data for ${FILE}"
                )
    endforeach()

    # Concatenate all files generated by geninfo to a single file per target.
    set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info")
    lcov_merge_files("${OUTFILE}" ${GENINFO_FILES})
    add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE})

    # add geninfo file generation to global lcov-capture target
    add_dependencies(lcov-capture ${TNAME}-geninfo)
    set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL
            ""
            )

    # Add target for generating html output for this target only.
    file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME})
    add_custom_target(${TNAME}-genhtml
            COMMAND ${GENHTML_BIN} --quiet --sort --prefix ${PROJECT_SOURCE_DIR}
            --baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info
            --output-directory ${LCOV_HTML_PATH}/${TNAME}
            --title "${CMAKE_PROJECT_NAME} - target ${TNAME}"
            ${GENHTML_CPPFILT_FLAG} ${OUTFILE}
            DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init
            )
endfunction (lcov_capture_tgt)


# This function will generate the global info file for all targets. It has to be
# called after all other CMake functions in the root CMakeLists.txt file, to get
# a full list of all targets that generate coverage data.
function (lcov_capture)
    # Skip this function (and do not create the following targets), if there are
    # no input files.
    if ("${LCOV_CAPTURE_FILES}" STREQUAL "")
        return()
    endif ()

    # Add a new target to merge the files of all targets.
    set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info")
    lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES})
    add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture)

    # Add a new global target for all lcov targets. This target could be used to
    # generate the lcov html output for the whole project instead of calling
    # <TARGET>-geninfo and <TARGET>-genhtml for each target. It will also be
    # used to generate a html site for all project data together instead of one
    # for each target.
    if (NOT TARGET lcov)
        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
                --title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}"
                ${GENHTML_CPPFILT_FLAG} ${OUTFILE}
                DEPENDS lcov-geninfo-init lcov-geninfo
                )
    endif ()
endfunction (lcov_capture)




# Add a new global target to generate the lcov html report for the whole project
# instead of calling <TARGET>-genhtml for each target (to create an own report
# for each target). Instead of the lcov target it does not require geninfo for
# all targets, so you have to call <TARGET>-geninfo to generate the info files
# the targets you'd like to have in your report or lcov-geninfo for generating
# info files for all targets before calling lcov-genhtml.
file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets)
if (NOT TARGET lcov-genhtml)
    add_custom_target(lcov-genhtml
            COMMAND ${GENHTML_BIN}
            --quiet
            --output-directory ${LCOV_HTML_PATH}/selected_targets
            --title \"${CMAKE_PROJECT_NAME} - targets  `find
            ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name
            \"all_targets.info\" -exec basename {} .info \\\;`\"
            --prefix ${PROJECT_SOURCE_DIR}
            --sort
            ${GENHTML_CPPFILT_FLAG}
            `find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name
            \"all_targets.info\"`
            )
endif (NOT TARGET lcov-genhtml)
