#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)
