# Distributed under the MIT License.
# See LICENSE.txt for details.

include(SpectreAddInterfaceLibraryHeaders)

# Get the list of absolute paths of header files for target `TARGET_NAME`.
#
# Sets the variable `HEADER_FILES` in parent scope to return the result
function(_absolute_header_paths TARGET_NAME)
  get_target_headers(${TARGET_NAME} _HEADER_FILES)
  get_property(
    _INCLUDE_DIR
    TARGET ${TARGET_NAME}
    PROPERTY INTERFACE_INCLUDE_DIRECTORIES
    )
  list(LENGTH _INCLUDE_DIR _NUMBER_OF_INCLUDE_DIRS)
  if(${_NUMBER_OF_INCLUDE_DIRS} GREATER 1)
    message(FATAL_ERROR
      "Currently _absolute_header_paths only supports a single "
      "INTERFACE_INCLUDE_DIRECTORIES for finding header files. Support for "
      "multiple INTERFACE_INCLUDE_DIRECTORIES can be added if needed.")
  endif(${_NUMBER_OF_INCLUDE_DIRS} GREATER 1)

  unset(_ABS_HEADER_FILES)
  foreach(HEADER ${_HEADER_FILES})
    if(IS_ABSOLUTE ${HEADER})
      list(APPEND _ABS_HEADER_FILES ${HEADER})
    else()
      list(APPEND _ABS_HEADER_FILES "${_INCLUDE_DIR}/${HEADER}")
    endif()
  endforeach(HEADER ${_HEADER_FILES})
  # "Return" HEADER_FILES by setting it in PARENT_SCOPE
  set(HEADER_FILES ${_ABS_HEADER_FILES} PARENT_SCOPE)
endfunction(_absolute_header_paths TARGET_NAME)

# Get the list of absolute paths of source files for target `TARGET_NAME`.
#
# Sets the variable `SOURCE_FILES` in parent scope to return the result
function(_absolute_source_paths TARGET_NAME)
  get_property(
    _SOURCE_FILES
    TARGET ${TARGET_NAME}
    PROPERTY SOURCES
    )
  get_property(
    _LOCATION
    TARGET ${TARGET_NAME}
    PROPERTY FOLDER
    )

  unset(_ABS_SOURCE_FILES)
  foreach(SOURCE ${_SOURCE_FILES})
    if(IS_ABSOLUTE ${SOURCE})
      list(APPEND _ABS_SOURCE_FILES ${SOURCE})
    else()
      list(APPEND _ABS_SOURCE_FILES "${_LOCATION}/${SOURCE}")
    endif()
  endforeach(SOURCE ${_SOURCE_FILES})
  # "Return" SOURCE_FILES by setting it in PARENT_SCOPE
  set(SOURCE_FILES ${_ABS_SOURCE_FILES} PARENT_SCOPE)
endfunction(_absolute_source_paths TARGET_NAME)

# Get the list of SpECTRE includes (i.e. not for 3rd party libraries)
# for the target `TARGET_NAME`.
#
# Sets the variable `SPECTRE_INCLUDES` in parent scope to return the result
function(_extract_spectre_includes FILE_NAME)
  file(READ ${FILE_NAME} FILE_CONTENTS)
  string(REGEX MATCHALL "\n#include \"[^\n]+\""
    _RAW_SPECTRE_INCLUDES ${FILE_CONTENTS})
  unset(SPECTRE_INCLUDES)
  foreach(INCLUDE ${_RAW_SPECTRE_INCLUDES})
    string(REPLACE "\n#include \"" "" INCLUDE "${INCLUDE}")
    string(REPLACE "\"" "" INCLUDE "${INCLUDE}")
    list(APPEND SPECTRE_INCLUDES ${INCLUDE})
  endforeach(INCLUDE ${_RAW_SPECTRE_INCLUDES})
  # "Return" SPECTRE_INCLUDES by setting it in PARENT_SCOPE
  set(SPECTRE_INCLUDES ${SPECTRE_INCLUDES} PARENT_SCOPE)
endfunction(_extract_spectre_includes FILE_NAME)

# Get the list of 3rd party library includes for the target `TARGET_NAME`.
#
# Sets the variable `TPL_INCLUDES` in parent scope to return the result
function(_extract_tpl_includes FILE_NAME)
  file(READ ${FILE_NAME} FILE_CONTENTS)
  string(REGEX MATCHALL "\n#include <[^\n]+>"
    _RAW_TPL_INCLUDES ${FILE_CONTENTS})
  unset(TPL_INCLUDES)
  foreach(INCLUDE ${_RAW_TPL_INCLUDES})
    string(REPLACE "\n#include <" "" INCLUDE "${INCLUDE}")
    string(REPLACE ">" "" INCLUDE "${INCLUDE}")
    list(APPEND TPL_INCLUDES ${INCLUDE})
  endforeach(INCLUDE ${_RAW_TPL_INCLUDES})
  # "Return" TPL_INCLUDES by setting it in PARENT_SCOPE
  set(TPL_INCLUDES ${TPL_INCLUDES} PARENT_SCOPE)
endfunction(_extract_tpl_includes FILE_NAME)

# Make the path of `HEADER_FILE` which belongs to the target `TARGET_NAME`
# an absolute path.
#
# Sets the variable `ABS_HEADER_FILE` in parent scope to return the result
function(_make_header_path_absolute TARGET_NAME HEADER_FILE)
  get_property(
    _INCLUDE_DIR
    TARGET ${TARGET_NAME}
    PROPERTY INTERFACE_INCLUDE_DIRECTORIES
    )
  if(IS_ABSOLUTE ${HEADER_FILE})
    set(_ABS_HEADER_FILE ${HEADER_FILE})
  else()
    set(_ABS_HEADER_FILE "${_INCLUDE_DIR}/${HEADER_FILE}")
  endif()
  # "Return" ABS_HEADER_FILE by setting it in PARENT_SCOPE
  set(ABS_HEADER_FILE ${_ABS_HEADER_FILE} PARENT_SCOPE)
endfunction(_make_header_path_absolute TARGET_NAME HEADER_FILE)

# Gets the dependencies for a list of includes of one of the source or
# header files of the target `TARGET_NAME`
#
# The `_get_deps_for_includes` function builds a list of all dependencies
# by finding the target that provides each header file in `INCLUDES_OF_FILE`.
# `INCLUDES_OF_FILE` must be a list of includes from a file (either header
# or source file) of the target `TARGET_NAME`. If `RECURSE_THROUGH_INCLUDES`
# is `TRUE` then for each header file in `INCLUDES_OF_FILE` that is also an
# include of the target `TARGET_NAME` then the header file will also have its
# dependencies searched and added to the list of dependencies.
#
# Example usage:
#
#   _get_deps_for_includes(
#     TARGET_C
#     "TARGET_A;TARGET_B;TARGET_C;TARGET_D"
#     "cmath;utility;vector"
#     FALSE
#     )
#
# Arguments:
#
# TARGET_NAME: the target for which to check the link libraries/dependencies
# LIST_OF_ALL_TARGETS: a list of all the targets that might be dependencies
#                      of the target ${TARGET_NAME}
# INCLUDES_OF_FILE:
#     a list of header files for which to get the dependencies.
# RECURSE_THROUGH_INCLUDES:
#     if TRUE then any include in any of ${INCLUDES_OF_FILE}
#     that is also an include of the target ${TARGET_NAME} has its
#     dependencies added.
#
# Since the recursion will cause an infinite loop if there are cyclic includes
# we limit the recursion depth to 20, at which point an error message is
# displayed that there may be cyclic dependencies
#
# Returns by setting `TARGET_DEPS` in parent scope
function(_get_deps_for_includes TARGET_NAME LIST_OF_ALL_TARGETS
    INCLUDES_OF_FILE RECURSE_THROUGH_INCLUDES)
  # Implement guard against infinite recursion when there are cyclic
  # includes in a library.
  get_property(
    GET_DEPS_FOR_SPECTRE_INCLUDES_PATH
    GLOBAL PROPERTY GET_DEPS_FOR_SPECTRE_INCLUDES_PATH
    )
  list(LENGTH GET_DEPS_FOR_SPECTRE_INCLUDES_PATH RECURSE_DEPTH)
  if(${RECURSE_DEPTH} GREATER 20)
    message(FATAL_ERROR
      " Found what appears to be a cyclic loop in the header files."
      " The header path is: ${GET_DEPS_FOR_SPECTRE_INCLUDES_PATH}"
      )
  endif(${RECURSE_DEPTH} GREATER 20)

  get_target_headers(${TARGET_NAME} TARGET_HEADER_FILES)

  unset(_TARGET_DEPS)
  # Now loop over all spectre targets to see if any of them
  # expose these includes
  foreach(CURRENT_INCLUDE ${INCLUDES_OF_FILE})
    # If we are checking header files and the header file is part of
    # this target, possibly recurse the header files, and skipping search
    # through targets for which one provides the header file (we know it's us).
    if(${CURRENT_INCLUDE} IN_LIST TARGET_HEADER_FILES)
      if(RECURSE_THROUGH_INCLUDES)
        # Sets ABS_HEADER_FILE
        _make_header_path_absolute(${TARGET_NAME} ${CURRENT_INCLUDE})
        # Sets SPECTRE_INCLUDES
        _extract_spectre_includes(${ABS_HEADER_FILE})
        set_property(
          GLOBAL APPEND PROPERTY GET_DEPS_FOR_CURRENT_INCLUDES_PATH
          ${CURRENT_INCLUDE}
          )
        # Sets TARGET_DEPS
        _get_deps_for_includes(${TARGET_NAME} "${LIST_OF_ALL_TARGETS}"
          "${CURRENT_INCLUDES}" ${RECURSE_THROUGH_INCLUDES})
        # "Pop" last element by setting the property to what it was at the
        # start of the function call.
        set_property(
          GLOBAL PROPERTY GET_DEPS_FOR_CURRENT_INCLUDES_PATH
          ${GET_DEPS_FOR_CURRENT_INCLUDES_PATH}
          )
        # Get the dependencies of the header file, since these are all
        # also PRIVATE dependencies of the target.
        foreach(_TARGET ${TARGET_DEPS})
          if(NOT ${_TARGET} IN_LIST _TARGET_DEPS)
            list(APPEND _TARGET_DEPS ${_TARGET})
          endif(NOT ${_TARGET} IN_LIST _TARGET_DEPS)
        endforeach(_TARGET ${TARGET_DEPS})
        continue()
      else(RECURSE_THROUGH_INCLUDES)
        continue()
      endif(RECURSE_THROUGH_INCLUDES)
    endif(${CURRENT_INCLUDE} IN_LIST TARGET_HEADER_FILES)

    unset(_TARGET_WITH_INCLUDE)
    foreach(DEP_TARGET ${LIST_OF_ALL_TARGETS})
      if(${DEP_TARGET} STREQUAL ${TARGET_NAME})
        continue()
      endif(${DEP_TARGET} STREQUAL ${TARGET_NAME})

      # Get the header files of DEP_TARGET and see if any of the
      # includes are part of DEP_TARGET.
      get_target_headers(${DEP_TARGET} _DEP_TARGET_HEADERS)
      if(${CURRENT_INCLUDE} IN_LIST _DEP_TARGET_HEADERS)
        set(_TARGET_WITH_INCLUDE ${DEP_TARGET})
        break()
      endif(${CURRENT_INCLUDE} IN_LIST _DEP_TARGET_HEADERS)
    endforeach(DEP_TARGET ${LIST_OF_ALL_TARGETS})
    if(NOT _TARGET_WITH_INCLUDE)
      message(FATAL_ERROR
        "No known targets supply the file ${CURRENT_INCLUDE} included in "
        "file ${HEADER_OR_SOURCE} of target ${TARGET_NAME}."
        )
    endif(NOT _TARGET_WITH_INCLUDE)

    if(NOT ${_TARGET_WITH_INCLUDE} IN_LIST _TARGET_DEPS)
      list(APPEND _TARGET_DEPS ${_TARGET_WITH_INCLUDE})
    endif(NOT ${_TARGET_WITH_INCLUDE} IN_LIST _TARGET_DEPS)
  endforeach(CURRENT_INCLUDE ${INCLUDES_OF_FILE})

  # "Return" TARGET_DEPS by setting it in PARENT_SCOPE
  set(TARGET_DEPS ${_TARGET_DEPS} PARENT_SCOPE)
endfunction(_get_deps_for_includes TARGET_NAME LIST_OF_ALL_TARGETS
  INCLUDES_OF_FILE RECURSE_THROUGH_INCLUDES)

# Returns the dependencies for the target
#
# Arguments
#
# TARGET_NAME:
#     the target for which to check the link libraries/dependencies
# LIST_OF_ALL_TARGETS:
#     a list of all the targets that might be dependencies
#     of the target ${TARGET_NAME}
# LIST_OF_FILES:
#     a list of the files for which to get the dependencies. Can be
#     header/tpp or source files.
# RECURSE_INCLUDES:
#     if TRUE then the dependencies of the target's included header files
#     will also be added. This is used when source files are passed because
#     the dependencies of the header files must be marked as PUBLIC
#
# Returns by setting `TARGET_DEPS` in parent scope
function(_get_deps_for_target TARGET_NAME LIST_OF_ALL_TARGETS
    LIST_OF_FILES RECURSE_INCLUDES)
  unset(_TARGET_DEPS)
  foreach(HEADER_OR_SOURCE ${LIST_OF_FILES})
    if(IS_ABSOLUTE "${HEADER_OR_SOURCE}")
      # Sets SPECTRE_INCLUDES
      _extract_spectre_includes("${HEADER_OR_SOURCE}")
    else()
      message(FATAL_ERROR
        "All paths of files passed to _get_deps_for_target must be absolute "
        "but received ${HEADER_OR_SOURCE}.")
    endif()

    # Sets TARGET_DEPS
    _get_deps_for_includes(${TARGET_NAME} "${LIST_OF_ALL_TARGETS}"
      "${SPECTRE_INCLUDES}" ${RECURSE_INCLUDES})
    foreach(_TARGET ${TARGET_DEPS})
      if(NOT ${_TARGET} IN_LIST _TARGET_DEPS)
        list(APPEND _TARGET_DEPS ${_TARGET})
      endif(NOT ${_TARGET} IN_LIST _TARGET_DEPS)
    endforeach(_TARGET ${TARGET_DEPS})

    # Third party library includes

    # Sets TPL_INCLUDES
    _extract_tpl_includes("${HEADER_OR_SOURCE}")
    # Sets TARGET_DEPS
    _get_deps_for_includes(${TARGET_NAME} "${LIST_OF_ALL_TARGETS}"
      "${TPL_INCLUDES}" FALSE)
    foreach(_TARGET ${TARGET_DEPS})
      if(NOT ${_TARGET} IN_LIST _TARGET_DEPS)
        list(APPEND _TARGET_DEPS ${_TARGET})
      endif(NOT ${_TARGET} IN_LIST _TARGET_DEPS)
    endforeach(_TARGET ${TARGET_DEPS})

  endforeach(HEADER_OR_SOURCE ${LIST_OF_FILES})
  # Alphabetize the list of dependencies
  list(SORT _TARGET_DEPS)
  # "Return" the target dependencies
  set(TARGET_DEPS ${_TARGET_DEPS} PARENT_SCOPE)
endfunction(_get_deps_for_target TARGET_NAME LIST_OF_FILES LIST_OF_ALL_TARGETS)

# Add the libraries for the `target_link_libraries` command
# for a target.
#
# Arguments
#
# TARGET_DEPS:
#     dependencies of the target
# TARGET_DEPS_TYPE:
#     the type of dependencies, INTERFACE/PRIVATE/PUBLIC
# TARGET_LINK_LIBRARIES_COMMAND:
#     the target_link_libraries command so far
#
# Returns by setting `TARGET_LINK_LIBS_COMMAND` in parent scope
function(_add_targets_to_link_libs TARGET_DEPS TARGET_DEPS_TYPE
    TARGET_LINK_LIBS_COMMAND)
  list(LENGTH TARGET_DEPS _NUM_TARGET_DEPS)
  set(_TARGET_LINK_LIBS_COMMAND
    "${TARGET_LINK_LIBS_COMMAND}"
    )
  if(${_NUM_TARGET_DEPS} GREATER 0)
    set(
      _TARGET_LINK_LIBS_COMMAND
      "${_TARGET_LINK_LIBS_COMMAND}\n"
      "   ${TARGET_DEPS_TYPE}"
      )
    foreach(_DEP ${TARGET_DEPS})
      set(
        _TARGET_LINK_LIBS_COMMAND
        "${_TARGET_LINK_LIBS_COMMAND}\n"
        "   ${_DEP}"
        )
    endforeach(_DEP ${_TARGET_DEPS})
    set(_TARGET_LINK_LIBS_COMMAND
      "${_TARGET_LINK_LIBS_COMMAND}\n "
      )
    string(REPLACE ";" "" _TARGET_LINK_LIBS_COMMAND
      ${_TARGET_LINK_LIBS_COMMAND})
  endif(${_NUM_TARGET_DEPS} GREATER 0)
  # "Return" the target dependencies
  set(TARGET_LINK_LIBS_COMMAND ${_TARGET_LINK_LIBS_COMMAND} PARENT_SCOPE)
endfunction(_add_targets_to_link_libs TARGET_DEPS TARGET_DEPS_TYPE
  TARGET_LINK_LIBS_COMMAND)

# Checks the dependencies for the target ${TARGET_NAME} and if they are
# incorrect produces an error message with the correct dependencies.
#
# TARGET_NAME:
#     the target for which to check the link libraries/dependencies
# TARGET_LINK_LIBS_COMMAND:
#     the target_link_libraries CMake command so far
# TARGET_INTERFACE_DEPS:
#     the INTERFACE link libraries for the target
# TARGET_PRIVATE_DEPS:
#     the PRIVATE link libraries for the target
# TARGET_PUBLIC_DEPS:
#     the PUBLIC link libraries for the target
# LIST_OF_ALLOWED_EXTRA_TARGETS:
#     a list of allowed extra dependencies. An example of an extra dependency
#     is the SpectreFlags target, which supplies no header files and there is
#     not strictly a dependency of ${TARGET_NAME} but can be specified as a
#     dependency in order to add compiler flags or definitions to the target.
# ERROR_ON_FAILURE:
#     if specified, then CMake will produce an error if the dependencies are
#     incorrect. Otherwise the variable TARGET_DEPENDENCIES_ERROR_MESSAGE
#     is set in the parent scope
function(_check_and_print_dependencies
    TARGET_NAME TARGET_LINK_LIBS_COMMAND
    TARGET_INTERFACE_DEPS TARGET_PRIVATE_DEPS TARGET_PUBLIC_DEPS
    LIST_OF_ALLOWED_EXTRA_TARGETS)
  cmake_parse_arguments(
    ARG "ERROR_ON_FAILURE" "" "" ${ARGN})

  get_property(
    _INTERFACE_LIBS
    TARGET ${TARGET_NAME}
    PROPERTY INTERFACE_LINK_LIBRARIES
    )
  unset(_MISSING_INTERFACE_LIBS)
  foreach(_INTERFACE_DEP ${TARGET_INTERFACE_DEPS})
    if(NOT ${_INTERFACE_DEP} IN_LIST _INTERFACE_LIBS)
      list(APPEND _MISSING_INTERFACE_LIBS ${_INTERFACE_DEP})
    endif(NOT ${_INTERFACE_DEP} IN_LIST _INTERFACE_LIBS)
  endforeach(_INTERFACE_DEP ${TARGET_INTERFACE_DEPS})

  get_target_property(
    TARGET_TYPE
    ${TARGET_NAME}
    TYPE
    )
  set(_NUM_MISSING_PRIVATE_LIBS 0)
  set(_NUM_MISSING_PUBLIC_LIBS 0)
  unset(_EXTRA_INTERFACE_LIBS)
  unset(_EXTRA_PRIVATE_LIBS)
  unset(_EXTRA_PUBLIC_LIBS)
  if(NOT ${TARGET_TYPE} STREQUAL INTERFACE_LIBRARY)
    get_property(
      _PRIVATE_LIBS
      TARGET ${TARGET_NAME}
      PROPERTY LINK_LIBRARIES
      )

    # Check to see if any interface-only dependencies are also
    # PRIVATE/PUBLIC
    foreach(_INTERFACE_DEP ${TARGET_INTERFACE_DEPS})
      if(${_INTERFACE_DEP} IN_LIST _PRIVATE_LIBS)
        list(APPEND _MISSING_INTERFACE_LIBS ${_INTERFACE_DEP})
      endif(${_INTERFACE_DEP} IN_LIST _PRIVATE_LIBS)
    endforeach(_INTERFACE_DEP ${TARGET_INTERFACE_DEPS})

    unset(_MISSING_PRIVATE_LIBS)
    foreach(_PRIVATE_DEP ${TARGET_PRIVATE_DEPS})
      if(NOT ${_PRIVATE_DEP} IN_LIST _PRIVATE_LIBS)
        list(APPEND _MISSING_PRIVATE_LIBS ${_PRIVATE_DEP})
      endif(NOT ${_PRIVATE_DEP} IN_LIST _PRIVATE_LIBS)
    endforeach(_PRIVATE_DEP ${TARGET_PRIVATE_DEPS})
    list(LENGTH _MISSING_PRIVATE_LIBS _NUM_MISSING_PRIVATE_LIBS)

    unset(_MISSING_PUBLIC_LIBS)
    foreach(_PUBLIC_DEP ${TARGET_PUBLIC_DEPS})
      if(NOT ${_PUBLIC_DEP} IN_LIST _PRIVATE_LIBS
          OR NOT ${_PUBLIC_DEP} IN_LIST _INTERFACE_LIBS)
        list(APPEND _MISSING_PUBLIC_LIBS ${_PUBLIC_DEP})
      endif(NOT ${_PUBLIC_DEP} IN_LIST _PRIVATE_LIBS
        OR NOT ${_PUBLIC_DEP} IN_LIST _INTERFACE_LIBS)
    endforeach(_PUBLIC_DEP ${TARGET_PUBLIC_DEPS})
    list(LENGTH _MISSING_PUBLIC_LIBS _NUM_MISSING_PUBLIC_LIBS)

    # Now check for extra target link libraries that aren't needed.
    unset(_PUBLIC_LIBS)
    foreach(_INTERFACE_LIB ${_INTERFACE_LIBS})
      if(${_INTERFACE_LIB} IN_LIST _PRIVATE_LIBS)
        list(APPEND _PUBLIC_LIBS ${_INTERFACE_LIB})
      endif(${_INTERFACE_LIB} IN_LIST _PRIVATE_LIBS)
    endforeach(_INTERFACE_LIB ${_INTERFACE_LIBS})

    foreach(_PUBLIC_LIB ${_PUBLIC_LIBS})
      list(REMOVE_ITEM _INTERFACE_LIBS ${_PUBLIC_LIB})
      list(REMOVE_ITEM _PRIVATE_LIBS ${_PUBLIC_LIB})
      if(NOT ${_PUBLIC_LIB} IN_LIST TARGET_PUBLIC_DEPS AND
          NOT ${_PUBLIC_LIB} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
        list(APPEND _EXTRA_PUBLIC_LIBS ${_PUBLIC_LIB})
      endif(NOT ${_PUBLIC_LIB} IN_LIST TARGET_PUBLIC_DEPS AND
          NOT ${_PUBLIC_LIB} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
    endforeach(_PUBLIC_LIB ${_PUBLIC_LIBS})

    foreach(_PRIVATE_LIB ${_PRIVATE_LIBS})
      if(NOT ${_PRIVATE_LIB} IN_LIST TARGET_PRIVATE_DEPS AND
          NOT ${_PRIVATE_LIB} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
        list(APPEND _EXTRA_PRIVATE_LIBS ${_PRIVATE_LIB})
      endif(NOT ${_PRIVATE_LIB} IN_LIST TARGET_PRIVATE_DEPS AND
          NOT ${_PRIVATE_LIB} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
    endforeach(_PRIVATE_LIB ${_PRIVATE_LIBS})

    foreach(_INTERFACE_LIB ${_INTERFACE_LIBS})
      if(NOT ${_INTERFACE_LIB} IN_LIST TARGET_INTERFACE_DEPS AND
          NOT ${_INTERFACE_LIB} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
        list(APPEND _EXTRA_INTERFACE_LIBS ${_INTERFACE_LIB})
      endif(NOT ${_INTERFACE_LIB} IN_LIST TARGET_INTERFACE_DEPS AND
          NOT ${_INTERFACE_LIB} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
    endforeach(_INTERFACE_LIB ${_INTERFACE_LIBS})
  else(NOT ${TARGET_TYPE} STREQUAL INTERFACE_LIBRARY)
    foreach(_INTERFACE_DEP ${_INTERFACE_LIBS})
      if(NOT ${_INTERFACE_DEP} IN_LIST TARGET_INTERFACE_DEPS AND
          NOT ${_INTERFACE_DEP} IN_LIST LIST_OF_ALLOWED_EXTRA_TARGETS)
        list(APPEND _EXTRA_INTERFACE_LIBS ${_INTERFACE_DEP})
      endif()
    endforeach(_INTERFACE_DEP ${_INTERFACE_LIBS})
  endif(NOT ${TARGET_TYPE} STREQUAL INTERFACE_LIBRARY)

  list(LENGTH _MISSING_INTERFACE_LIBS _NUM_MISSING_INTERFACE_LIBS)

  list(LENGTH _EXTRA_INTERFACE_LIBS _NUM_EXTRA_INTERFACE_LIBS)
  list(LENGTH _EXTRA_PRIVATE_LIBS _NUM_EXTRA_PRIVATE_LIBS)
  list(LENGTH _EXTRA_PUBLIC_LIBS _NUM_EXTRA_PUBLIC_LIBS)

  set(_MISSING_LIBS FALSE)
  set(_ERROR_MSG " Missing dependencies for target ${TARGET_NAME}")
  if(${_NUM_MISSING_INTERFACE_LIBS} GREATER 0)
    set(_ERROR_MSG
      "${_ERROR_MSG}\n Missing interface libs:\n   ${_MISSING_INTERFACE_LIBS}")
    set(_MISSING_LIBS TRUE)
  endif(${_NUM_MISSING_INTERFACE_LIBS} GREATER 0)
  if(${_NUM_MISSING_PRIVATE_LIBS} GREATER 0)
    set(_ERROR_MSG
      "${_ERROR_MSG}\n Missing private libs:\n   ${_MISSING_PRIVATE_LIBS}")
    set(_MISSING_LIBS TRUE)
  endif(${_NUM_MISSING_PRIVATE_LIBS} GREATER 0)
  if(${_NUM_MISSING_PUBLIC_LIBS} GREATER 0)
    set(_ERROR_MSG
      "${_ERROR_MSG}\n Missing public libs:\n   ${_MISSING_PUBLIC_LIBS}")
    set(_MISSING_LIBS TRUE)
  endif(${_NUM_MISSING_PUBLIC_LIBS} GREATER 0)

  if(${_NUM_EXTRA_INTERFACE_LIBS} GREATER 0 OR
      ${_NUM_EXTRA_PRIVATE_LIBS} GREATER 0 OR
      ${_NUM_EXTRA_PUBLIC_LIBS} GREATER 0)
    set(_ERROR_MSG " Extra dependencies for target ${TARGET_NAME}")
  endif()
  if(${_NUM_EXTRA_INTERFACE_LIBS} GREATER 0)
    set(_ERROR_MSG
      "${_ERROR_MSG}\n Extra interface libs:\n   ${_EXTRA_INTERFACE_LIBS}")
    set(_MISSING_LIBS TRUE)
  endif(${_NUM_EXTRA_INTERFACE_LIBS} GREATER 0)
  if(${_NUM_EXTRA_PRIVATE_LIBS} GREATER 0)
    set(_ERROR_MSG
      "${_ERROR_MSG}\n Extra private libs:\n   ${_EXTRA_PRIVATE_LIBS}")
    set(_MISSING_LIBS TRUE)
  endif(${_NUM_EXTRA_PRIVATE_LIBS} GREATER 0)
  if(${_NUM_EXTRA_PUBLIC_LIBS} GREATER 0)
    set(_ERROR_MSG
      "${_ERROR_MSG}\n Extra public libs:\n   ${_EXTRA_PUBLIC_LIBS}")
    set(_MISSING_LIBS TRUE)
  endif(${_NUM_EXTRA_PUBLIC_LIBS} GREATER 0)

  if(NOT ARG_ERROR_ON_FAILURE)
    set(
      TARGET_DEPS_ERROR_MESSAGE
      ""
      PARENT_SCOPE
      )
  endif(NOT ARG_ERROR_ON_FAILURE)

  if(_MISSING_LIBS)
    set(TARGET_LINK_LIBS_COMMAND
      " target_link_libraries(\n"
      "   ${TARGET_NAME}"
      )
    _add_targets_to_link_libs(
      "${TARGET_INTERFACE_DEPS}" "INTERFACE"
      "${TARGET_LINK_LIBS_COMMAND}")
    _add_targets_to_link_libs(
      "${TARGET_PRIVATE_DEPS}" "PRIVATE"
      "${TARGET_LINK_LIBS_COMMAND}")
    _add_targets_to_link_libs(
      "${TARGET_PUBLIC_DEPS}" "PUBLIC"
      "${TARGET_LINK_LIBS_COMMAND}")
    set(TARGET_LINK_LIBS_COMMAND
      "${TARGET_LINK_LIBS_COMMAND}  )"
      )

    if(${ARG_ERROR_ON_FAILURE})
      message(FATAL_ERROR
        "${_ERROR_MSG}\n \n"
        " Correct target_link_libraries command is:\n"
        "${TARGET_LINK_LIBS_COMMAND}"
        )
    else(${ARG_ERROR_ON_FAILURE})
      set(
        TARGET_DEPS_ERROR_MESSAGE
        "${_ERROR_MSG}\n \n\
 Correct target_link_libraries command is:\n\
${TARGET_LINK_LIBS_COMMAND}"
        PARENT_SCOPE
        )
    endif(${ARG_ERROR_ON_FAILURE})
  endif(_MISSING_LIBS)
endfunction(_check_and_print_dependencies
  TARGET_NAME TARGET_LINK_LIBS_COMMAND
  TARGET_INTERFACE_DEPS TARGET_PRIVATE_DEPS TARGET_PUBLIC_DEPS
  LIST_OF_ALLOWED_EXTRA_TARGETS)

# Check that the link libraries of the target are correct and if not,
# provide an error with the correct target link libraries.
#
# Example usage:
#
#    check_target_dependencies(
#      TARGET ${TARGET_TO_CHECK}
#      ALL_TARGETS
#      ${SPECTRE_TPLS}
#      ${SPECTRE_LIBS}
#      ALLOWED_EXTRA_TARGETS
#      SpectreFlags
#      ERROR_ON_FAILURE
#      )
#
# Arguments:
#
# TARGET:
#     the target for which to check the link libraries/dependencies
# ALL_TARGETS:
#     a list of all the targets that might be dependencies of the target
#     ${TARGET}
# ALLOWED_EXTRA_TARGETS:
#     a list of allowed extra dependencies. An example of an extra dependency
#     is the SpectreFlags target, which supplies no header files and therefore
#     is not strictly a dependency of ${TARGET} but can be specified as
#     dependency in order to add compiler flags or definitions to the target.
# ERROR_ON_FAILURE:
#     if specified, then CMake will produce an error if the dependencies are
#     incorrect. Otherwise the variable TARGET_DEPENDENCIES_ERROR_MESSAGE
#     is set in the parent scope
function(check_target_dependencies)
  cmake_parse_arguments(
    ARG
    "ERROR_ON_FAILURE"
    "TARGET"
    "ALL_TARGETS;ALLOWED_EXTRA_TARGETS" ${ARGN})

  # Sets HEADER_FILES
  _absolute_header_paths(${ARG_TARGET})

  # Sets TARGET_DEPS
  _get_deps_for_target(${ARG_TARGET}
    "${ARG_ALL_TARGETS}" "${HEADER_FILES}" FALSE)
  set(_TARGET_INTERFACE_DEPS ${TARGET_DEPS})
  list(REMOVE_ITEM _TARGET_INTERFACE_DEPS Stl)

  get_target_property(
    TARGET_TYPE
    ${ARG_TARGET}
    TYPE
    )

  unset(_TARGET_PRIVATE_DEPS)
  unset(_TARGET_PUBLIC_DEPS)
  if(NOT ${TARGET_TYPE} STREQUAL INTERFACE_LIBRARY)
    # Sets SOURCE_FILES
    _absolute_source_paths(${ARG_TARGET})

    # Sets TARGET_DEPS
    _get_deps_for_target(${ARG_TARGET}
      "${ARG_ALL_TARGETS}" "${SOURCE_FILES}" TRUE)
    set(_TARGET_PRIVATE_DEPS ${TARGET_DEPS})
    # We only use the Stl target to be able to keep track of all header files
    # and to be able to claim with certainly that an extra header file is
    # present. Since there are no explicit includes or link dependencies that
    # need to be added, we don't want the extra noise of explicitly depending
    # on Stl everywhere in the code and so we remove it from the list.
    list(REMOVE_ITEM _TARGET_PRIVATE_DEPS Stl)

    # Now need to figure out what the PUBLIC libraries are
    unset(_TARGET_PUBLIC_DEPS)
    foreach(_PRIVATE_DEP ${_TARGET_PRIVATE_DEPS})
      if(${_PRIVATE_DEP} IN_LIST _TARGET_INTERFACE_DEPS)
        list(APPEND _TARGET_PUBLIC_DEPS ${_PRIVATE_DEP})
      endif(${_PRIVATE_DEP} IN_LIST _TARGET_INTERFACE_DEPS)
    endforeach(_PRIVATE_DEP ${_TARGET_PRIVATE_DEPS})
    # Remove PRIVATE deps from INTERFACE and PUBLIC lists
    foreach(_PUBLIC_DEP ${_TARGET_PUBLIC_DEPS})
      list(REMOVE_ITEM _TARGET_PRIVATE_DEPS ${_PUBLIC_DEP})
      list(REMOVE_ITEM _TARGET_INTERFACE_DEPS ${_PUBLIC_DEP})
    endforeach(_PUBLIC_DEP ${_TARGET_PUBLIC_DEPS})
  endif(NOT ${TARGET_TYPE} STREQUAL INTERFACE_LIBRARY)

  set(ERROR_ON_FAILURE "")
  if(ARG_ERROR_ON_FAILURE)
    set(ERROR_ON_FAILURE ERROR_ON_FAILURE)
  endif(ARG_ERROR_ON_FAILURE)
  _check_and_print_dependencies(
    ${ARG_TARGET}
    "${TARGET_LINK_LIBS_COMMAND}"
    "${_TARGET_INTERFACE_DEPS}"
    "${_TARGET_PRIVATE_DEPS}"
    "${_TARGET_PUBLIC_DEPS}"
    "${ARG_ALLOWED_EXTRA_TARGETS}"
    ${ERROR_ON_FAILURE}
    )
  if(NOT ARG_ERROR_ON_FAILURE)
    set(
      TARGET_DEPS_ERROR_MESSAGE
      "${TARGET_DEPS_ERROR_MESSAGE}"
      PARENT_SCOPE
      )
  endif(NOT ARG_ERROR_ON_FAILURE)
endfunction(check_target_dependencies)
