# ============================================================================
# Copyright (c) 2011-2012 University of Pennsylvania
# Copyright (c) 2013-2014 Andreas Schuh
# All rights reserved.
#
# See COPYING file for license information or visit
# http://opensource.andreasschuh.com/cmake-basis/download.html#license
# ============================================================================

##############################################################################
# @file  ExportTools.cmake
# @brief Functions and macros for the export of targets.
#
# @ingroup CMakeTools
##############################################################################

if (__BASIS_EXPORTIMPORTTOOLS_INCLUDED)
  return ()
else ()
  set (__BASIS_EXPORTIMPORTTOOLS_INCLUDED TRUE)
endif ()


## @addtogroup CMakeUtilities
#  @{


# ----------------------------------------------------------------------------
## @brief Add target to export set.
#
# Targets of a subproject are added to the export set named after the subproject
# itself. All other targets, including in particular those of other modules,
# are added to the export set named after the top-level project.
#
# @param[out] EXPORT_OPTION Export option for install() command including
#                           the EXPORT option name. Set to an empty string
#                           if target is not installed.
# @param[in]  TEST          Whether given target is a test executable or library.
# @param[in]  ARGN          Optional installation destinations.
function (basis_add_export_target EXPORT_OPTION TARGET_UID TEST)
  if (PROJECT_IS_SUBPROJECT)
    set (EXPORT_SET "${PROJECT_NAME}")
  else ()
    set (EXPORT_SET "${TOPLEVEL_PROJECT_NAME}")
  endif ()
  if (TEST)
    basis_set_project_property (PROJECT "${EXPORT_SET}" APPEND PROPERTY TEST_EXPORT_TARGETS "${TARGET_UID}")
  else ()
    basis_set_project_property (PROJECT "${EXPORT_SET}" APPEND PROPERTY EXPORT_TARGETS "${TARGET_UID}")
    if (ARGN)
      basis_set_project_property (PROJECT "${EXPORT_SET}" APPEND PROPERTY INSTALL_EXPORT_TARGETS "${TARGET_UID}")
    endif ()
    set (EXPORT_OPT "EXPORT;${EXPORT_SET}" PARENT_SCOPE)
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add target to custom export set.
#
# Targets of a subproject are added to the export set named after the subproject
# itself. All other targets, including in particular those of other modules,
# are added to the export set named after the top-level project.
#
# @param[in]  TARGET_UID UID of target to add to the export set.
# @param[in]  TEST       Whether given target is a test executable or library.
function (basis_add_custom_export_target TARGET_UID TEST)
  if (PROJECT_IS_SUBPROJECT)
    set (EXPORT_SET "${PROJECT_NAME}")
  else ()
    set (EXPORT_SET "${TOPLEVEL_PROJECT_NAME}")
  endif ()
  if (TEST)
    basis_set_project_property (PROJECT "${EXPORT_SET}" APPEND PROPERTY TEST_EXPORT_TARGETS "${TARGET_UID}")
  else ()
    basis_set_project_property (PROJECT "${EXPORT_SET}" APPEND PROPERTY CUSTOM_EXPORT_TARGETS "${TARGET_UID}")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Get soname of object file.
#
# This function extracts the soname from object files in the ELF format on
# systems where the objdump command is available. On all other systems,
# an empty string is returned.
#
# @param [out] SONAME  The soname of the object file.
# @param [in]  OBJFILE Object file in ELF format.
function (basis_get_soname SONAME OBJFILE)
  # get absolute path of object file
  basis_get_target_uid (TARGET_UID ${OBJFILE})
  if (TARGET TARGET_UID)
    basis_get_target_location (OBJFILE ${TARGET_UID} ABSOLUTE)
  else ()
    get_filename_component (OBJFILE "${OBJFILE}" ABSOLUTE)
  endif ()
  # usually CMake did this already
  find_program (CMAKE_OBJDUMP NAMES objdump DOC "The objdump command")
  # run objdump and extract soname
  execute_process (
    COMMAND ${CMAKE_OBJDUMP} -p "${OBJFILE}"
    COMMAND sed -n "-e's/^[[:space:]]*SONAME[[:space:]]*//p'"
    RESULT_VARIABLE STATUS
    OUTPUT_VARIABLE SONAME_OUT
    ERROR_QUIET
  )
  # return
  if (STATUS EQUAL 0)
    set (${SONAME} "${SONAME_OUT}" PARENT_SCOPE)
  else ()
    set (${SONAME} "" PARENT_SCOPE)
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Generate header of exports file.
function (basis_export_header CODE)
  set (C "# Generated by BASIS\n\n")
  set (C "${C}if (\"\${CMAKE_MAJOR_VERSION}.\${CMAKE_MINOR_VERSION}\" LESS 2.8)\n")
  set (C "${C}  message (FATAL_ERROR \"CMake >= 2.8.4 required\")\n")
  set (C "${C}endif ()\n")
  set (C "${C}cmake_policy (PUSH)\n")
  set (C "${C}cmake_policy (VERSION 2.8.4)\n")
  set (C "${C}#----------------------------------------------------------------\n")
  set (C "${C}# Generated CMake target import file.\n")
  set (C "${C}#----------------------------------------------------------------\n")
  set (C "${C}\n# Commands may need to know the format version.\n")
  set (C "${C}set (CMAKE_IMPORT_FILE_VERSION 1)\n")
  set (${CODE} "${C}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add code to compute prefix relative to @c INSTALL_CONFIG_DIR.
function (basis_export_prefix CODE)
  set (C "\n# Compute the installation prefix relative to this file.\n")
  set (C "${C}get_filename_component (_IMPORT_PREFIX \"\${CMAKE_CURRENT_LIST_FILE}\" PATH)\n")
  string (REGEX REPLACE "[/\\]" ";" DIRS "${INSTALL_CONFIG_DIR}")
  foreach (D IN LISTS DIRS)
    set (C "${C}get_filename_component (_IMPORT_PREFIX \"\${_IMPORT_PREFIX}\" PATH)\n")
  endforeach ()
  set (${CODE} "${${CODE}}${C}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add code to add import targets.
function (basis_export_import_targets CODE)
  set (C)
  foreach (T IN LISTS ARGN)
    basis_get_fully_qualified_target_uid (UID "${T}")
    set (C "${C}\n# Create import target \"${UID}\"\n")
    get_target_property (BASIS_TYPE ${T} "BASIS_TYPE")
    if (BASIS_TYPE MATCHES "EXECUTABLE")
      set (C "${C}add_executable (${UID} IMPORTED)\n")
    elseif (BASIS_TYPE MATCHES "LIBRARY|MODULE|MEX")
      if (BASIS_TYPE MATCHES "MEX|MCC")
        set (TYPE SHARED)
      elseif (BASIS_TYPE MATCHES "SCRIPT")
        set (TYPE UNKNOWN)
      else ()
        string (REGEX REPLACE "_LIBRARY" "" TYPE "${BASIS_TYPE}")
      endif ()
      set (C "${C}add_library (${UID} ${TYPE} IMPORTED)\n")
    else ()
      message (FATAL_ERROR "Cannot export target ${T} of type ${BASIS_TYPE}! Use NOEXPORT option.")
    endif ()
    set (C "${C}set_target_properties (${UID} PROPERTIES BASIS_TYPE \"${BASIS_TYPE}\")\n")
  endforeach ()
  set (${CODE} "${${CODE}}${C}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add code to set properties of imported targets for build tree.
function (basis_export_build_properties CODE)
  set (C)
  if (CMAKE_BUILD_TYPE)
    set (CONFIG "${CMAKE_BUILD_TYPE}")
  else ()
    set (CONFIG "noconfig")
  endif ()
  string (TOUPPER "${CONFIG}" CONFIG_U)
  foreach (T IN LISTS ARGN)
    basis_get_fully_qualified_target_uid (UID "${T}")
    get_target_property (BASIS_TYPE ${T} BASIS_TYPE)
    set (C "${C}\n# Import target \"${UID}\" for configuration \"${CONFIG}\"\n")
    set (C "${C}set_property (TARGET ${UID} APPEND PROPERTY IMPORTED_CONFIGURATIONS ${CONFIG_U})\n")
    set (C "${C}set_target_properties (${UID} PROPERTIES\n")
    if (BASIS_TYPE MATCHES "SCRIPT|LIBRARY|MEX")
      get_target_property (LANGUAGE ${T} LANGUAGE)
      if (LANGUAGE)
        set (C "${C}  IMPORTED_LINK_INTERFACE_LANGUAGES_${CONFIG_U} \"${LANGUAGE}\"\n")
      endif ()
      get_target_property (LINK_DEPENDS ${T} LINK_DEPENDS)
      set (LINK_UIDS)
      foreach (LINK_DEPEND IN LISTS LINK_DEPENDS)
        basis_get_fully_qualified_target_uid (LINK_UID ${LINK_DEPEND})
        list (APPEND LINK_UIDS "${LINK_UID}")
      endforeach ()
      set (C "${C}  IMPORTED_LINK_INTERFACE_LIBRARIES_${CONFIG_U} \"${LINK_UIDS}\"\n")
    endif ()
    basis_get_target_location (LOCATION ${T} ABSOLUTE)
    set (C "${C}  IMPORTED_LOCATION_${CONFIG_U} \"${LOCATION}\"\n")
    set (C "${C}  )\n")
  endforeach ()
  set (${CODE} "${${CODE}}${C}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add code to set properties of imported targets for installation.
function (basis_export_install_properties CODE)
  set (C)
  if (CMAKE_BUILD_TYPE)
    set (CONFIG "${CMAKE_BUILD_TYPE}")
  else ()
    set (CONFIG "noconfig")
  endif ()
  string (TOUPPER "${CONFIG}" CONFIG_U)
  foreach (T IN LISTS ARGN)
    basis_get_fully_qualified_target_uid (UID "${T}")
    get_target_property (BASIS_TYPE ${T} BASIS_TYPE)
    set (C "${C}\n# Import target \"${UID}\" for configuration \"${CONFIG}\"\n")
    set (C "${C}set_property (TARGET ${UID} APPEND PROPERTY IMPORTED_CONFIGURATIONS ${CONFIG_U})\n")
    set (C "${C}set_target_properties (${UID} PROPERTIES\n")
    basis_get_target_location (LOCATION ${T} POST_INSTALL_RELATIVE)
    set (C "${C}  IMPORTED_LOCATION_${CONFIG_U} \"\${_IMPORT_PREFIX}/${LOCATION}\"\n")
    if (BASIS_TYPE MATCHES "SCRIPT|LIBRARY|MEX")
      get_target_property (LANGUAGE ${T} LANGUAGE)
      if (LANGUAGE)
        set (C "${C}  IMPORTED_LINK_INTERFACE_LANGUAGES_${CONFIG_U} \"${LANGUAGE}\"\n")
      endif ()
      get_target_property (LINK_DEPENDS ${T} LINK_DEPENDS)
      set (LINK_UIDS)
      foreach (LINK_DEPEND IN LISTS LINK_DEPENDS)
        basis_get_fully_qualified_target_uid (LINK_UID ${LINK_DEPEND})
        list (APPEND LINK_UIDS "${LINK_UID}")
      endforeach ()
      set (C "${C}  IMPORTED_LINK_INTERFACE_LIBRARIES_${CONFIG_U} \"${LINK_UIDS}\"\n")
    endif ()
    set (C "${C}  )\n")
  endforeach ()
  set (${CODE} "${${CODE}}${C}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add footer of exports file.
function (basis_export_footer CODE)
  set (C "\n# Cleanup temporary variables.\n")
  set (C "${C}set (_IMPORT_PREFIX)\n")
  set (C "${C}\n# Commands beyond this point should not need to know the version.\n")
  set (C "${C}set (CMAKE_IMPORT_FILE_VERSION)\n")
  set (C "${C}cmake_policy (POP)\n")
  set (${CODE} "${${CODE}}${C}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Export all targets added by basis_add_* commands.
function (basis_export_targets)
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (ARGN "" "FILE;CUSTOM_FILE" "" ${ARGN})

  if (NOT ARGN_FILE)
    message (FATAL_ERROR "basis_export_targets(): FILE option is required!")
  endif ()
  if (NOT ARGN_CUSTOM_FILE)
    message (FATAL_ERROR "basis_export_targets(): CUSTOM_FILE option is required!")
  endif ()

  if (IS_ABSOLUTE ${ARGN_FILE})
    message (FATAL_ERROR "basis_export_targets(): FILE option argument must be a relative path!")
  endif ()
  if (IS_ABSOLUTE ${ARGN_CUSTOM_FILE})
    message (FATAL_ERROR "basis_export_targets(): CUSTOM_FILE option argument must be a relative path!")
  endif ()

  # --------------------------------------------------------------------------
  # export non-custom targets
  basis_get_project_property (EXPORT_TARGETS)
  if (EXPORT_TARGETS)
    if (BASIS_USE_TARGET_UIDS AND BASIS_USE_FULLY_QUALIFIED_UIDS)
      set (NAMESPACE_OPT)
    elseif (TOPLEVEL_PROJECT_NAMESPACE_CMAKE)
      set (NAMESPACE_OPT NAMESPACE "${TOPLEVEL_PROJECT_NAMESPACE_CMAKE}.")
    endif ()
    export (
      TARGETS   ${EXPORT_TARGETS}
      FILE      "${PROJECT_BINARY_DIR}/${ARGN_FILE}"
      ${NAMESPACE_OPT}
    )
    basis_get_project_property (INSTALL_EXPORT_TARGETS)
    if (INSTALL_EXPORT_TARGETS)
      foreach (COMPONENT "${BASIS_RUNTIME_COMPONENT}" "${BASIS_LIBRARY_COMPONENT}")
        install (
          EXPORT      "${PROJECT_NAME}"
          DESTINATION "${INSTALL_CONFIG_DIR}"
          FILE        "${ARGN_FILE}"
          COMPONENT   "${COMPONENT}"
          ${NAMESPACE_OPT}
        )
      endforeach ()
    endif ()
  endif ()

  # --------------------------------------------------------------------------
  # export custom targets and/or test targets
  basis_get_project_property (CUSTOM_EXPORT_TARGETS)
  basis_get_project_property (TEST_EXPORT_TARGETS)

  if (CUSTOM_EXPORT_TARGETS OR TEST_EXPORT_TARGETS)

    # write exports for build tree
    basis_export_header (CONTENT)
    basis_export_import_targets (CONTENT ${CUSTOM_EXPORT_TARGETS} ${TEST_EXPORT_TARGETS})
    basis_export_build_properties (CONTENT ${CUSTOM_EXPORT_TARGETS}  ${TEST_EXPORT_TARGETS})
    basis_export_footer (CONTENT)

    file (WRITE "${PROJECT_BINARY_DIR}/${ARGN_CUSTOM_FILE}" "${CONTENT}")
    unset (CONTENT)

    # write exports for installation - excluding test targets
    if (CUSTOM_EXPORT_TARGETS)
      set (INSTALL_EXPORT_FILE "${PROJECT_BINARY_DIR}/CMakeFiles/Export/${INSTALL_CONFIG_DIR}/${ARGN_CUSTOM_FILE}")

      basis_export_header (CONTENT)
      basis_export_prefix (CONTENT)
      basis_export_import_targets (CONTENT ${CUSTOM_EXPORT_TARGETS})
      basis_export_install_properties (CONTENT ${CUSTOM_EXPORT_TARGETS})
      basis_export_footer (CONTENT)

      file (WRITE "${INSTALL_EXPORT_FILE}" "${CONTENT}")
      unset (CONTENT)

      install (
        FILES       "${INSTALL_EXPORT_FILE}"
        DESTINATION "${INSTALL_CONFIG_DIR}"
      )
    endif ()

  endif ()
endfunction ()


## @}
# end of Doxygen group
