#-------------------------------------------------------------------------------
# Authors: Marco Biasini, Juergen Haas, Andreas Schenk
#
# This file contains a bunch of useful macros to facilitate the build-system
# configuration for the modules.
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
# map macro
#
# this function emulates a map/dict data type
#-------------------------------------------------------------------------------

function(map COMMAND MAPNAME)
  set (_KEYS ${MAPNAME}_MAP_KEYS )
  set (_VALUES ${MAPNAME}_MAP_VALUES)
  if(${COMMAND} STREQUAL SET)
    list(REMOVE_AT ARGN 0)
    list(FIND ${_KEYS} ${ARGV2} _MAP_INDEX)
    if(_MAP_INDEX EQUAL -1)
      list(APPEND ${_KEYS} ${ARGV2})
      set(${_KEYS} ${${_KEYS}} PARENT_SCOPE)
      set(${_VALUES}_${ARGV2}  ${ARGN} PARENT_SCOPE)
    else()
      set(${_VALUES}_${ARGV2}  ${ARGN} PARENT_SCOPE)
    endif()
  elseif(${COMMAND} STREQUAL GET)
    list(FIND ${_KEYS} ${ARGV2} _MAP_INDEX)
    if(_MAP_INDEX EQUAL -1)
      MESSAGE(FATAL_ERROR "Unknown key: " ${ARGV2})
    endif()
    set(${ARGV3} ${${_VALUES}_${ARGV2}} PARENT_SCOPE)
  elseif(${COMMAND} STREQUAL KEYS)
    set(${ARGV2} ${${_KEYS}} PARENT_SCOPE)
  elseif(${COMMAND} STREQUAL CREATE)
    set(${_KEYS}  "" PARENT_SCOPE)
  elseif(${COMMAND} STREQUAL LENGTH)
    list(LENGTH ${_KEYS} _L)
    set(${ARGV2} ${_L} PARENT_SCOPE)
  else()
    MESSAGE(FATAL_ERROR "Unknown map command:" ${COMMAND})
  endif()
endfunction()


#-------------------------------------------------------------------------------
# check_architecture
#
# detect architecture based on void pointer size. the output is stored in the
# 3 variables OS_32_BITS, OS_64_BITS and CMAKE_NATIVE_ARCH. The former two are
# set to 0 and 1 accordingly and CMAKE_NATIVE_ARCH is set to the 32 or 64 bit.
#-------------------------------------------------------------------------------
macro(check_architecture)
  include(CheckTypeSize)
  check_type_size(void*  SIZEOF_VOID_PTR)
  if(${SIZEOF_VOID_PTR} MATCHES "^8$")
    set(OS_32_BITS 0)
    set(OS_64_BITS 1)
    set(CMAKE_NATIVE_ARCH 64)
  else()
    set(OS_32_BITS 1)
    set(OS_64_BITS 0)
    set(CMAKE_NATIVE_ARCH 32)
  endif()
endmacro()

#-------------------------------------------------------------------------------
# this macro has been adapted from
# http://www.cmake.org/Wiki/CMakeMacroParseArguments
#-------------------------------------------------------------------------------
macro(parse_argument_list PREFIX ARG_NAMES OPT_NAMES)
  set(_DEFAULT_ARGS)
  # reset variables
  foreach(${_ARG_NAME} ${ARG_NAMES})
    set(PREFIX_${_ARG_NAME})
  endforeach()
  foreach(_OPT_NAME ${OPT_NAMES})
    set(PREFIX_${_OPT_NAME} FALSE)
  endforeach()
  set(_CURR_ARG_NAME DEF_ARGS)
  set(_CURR_ARG_LIST)
  # loop over parameter list and split by ARG_NAMES
  foreach(_ARG ${ARGN})
    set(_LARG_NAMES ${ARG_NAMES})  
    list(FIND _LARG_NAMES ${_ARG} _IS_ARG_NAME)
    if (_IS_ARG_NAME GREATER -1)
      set(${PREFIX}_${_CURR_ARG_NAME} ${_CURR_ARG_LIST})
      set(_CURR_ARG_NAME "${_ARG}")
      set(_CURR_ARG_LIST)
    else()
    set(_LOPT_NAMES ${OPT_NAMES})  
      list(FIND _LOPT_NAMES ${_ARG} _IS_OPT_NAME)
      if (_IS_OPT_NAME GREATER -1)
        set(${PREFIX}_${_ARG} TRUE)
      else()
        list(APPEND _CURR_ARG_LIST "${_ARG}")
      endif()
    endif()
  endforeach()
  set(${PREFIX}_${_CURR_ARG_NAME} ${_CURR_ARG_LIST})
endmacro()

#-------------------------------------------------------------------------------
# copy_if_different
#
# copies file from source directory to destination directory, but only if its
# content changed.
#-------------------------------------------------------------------------------
macro(copy_if_different FROM_DIR TO_DIR FILES TARGETS TARGET)
  foreach(SRC ${FILES})
      set(SRCFILE ${SRC})
      if("${FROM_DIR}" STREQUAL "" OR "${FROM_DIR}" STREQUAL "./")
          set(FROM ${SRC})
      else()
          set(FROM ${FROM_DIR}/${SRC})
      endif()
      if("${TO_DIR}" STREQUAL "")
          set(TO ${SRCFILE})
      else()
          get_filename_component(TOFILE ${SRC} NAME)      
          set(TO ${TO_DIR}/${TOFILE})
      endif()
      file(MAKE_DIRECTORY  ${TO_DIR})
      add_custom_command(TARGET "${TARGET}" PRE_BUILD
          DEPENDS ${FROM}
          COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FROM} ${TO})
  endforeach()
endmacro()


#-------------------------------------------------------------------------------
# parse_file_list
#
# this macro splits a list of files with IN_DIR statements and fills them into a map
# where the key is the directory name
#-------------------------------------------------------------------------------
macro(parse_file_list FILELIST FILEMAP)
  set(_EXPECT_IN_DIR FALSE)
  map(CREATE ${FILEMAP})
  set(_CURRENT_LIST)
  foreach(_ITEM ${FILELIST})
    if (_ITEM STREQUAL "IN_DIR")
      set(_EXPECT_IN_DIR TRUE)
    else()
      if (_EXPECT_IN_DIR)
        set(_EXPECT_IN_DIR FALSE)
        map(SET ${FILEMAP} ${_ITEM} ${_CURRENT_LIST})
        set(_CURRENT_LIST)
      else()
        list(APPEND _CURRENT_LIST "${_ITEM}")
      endif()
    endif()
  endforeach()
  if(_CURRENT_LIST)
    map(SET ${FILEMAP} "." ${_CURRENT_LIST})
  endif()
endmacro()


#-------------------------------------------------------------------------------
# Synopsis:
#   module(NAME name SOURCES source1 source2 HEADERS header1 header2 
#          [IN_DIR dir] [header3 header4 [IN_DIR dir]] [DEPENDS_ON dep1 dep2]
#          [HEADER_OUTPUT_DIR dir]
#          [LINK link_cmds])
# Description:
#   Define an OpenStructure module.
#-------------------------------------------------------------------------------
macro(module)
  #-----------------------------------------------------------------------------
  # deal with arguments
  #-----------------------------------------------------------------------------
  set(_ARGS "NAME;SOURCES;HEADERS;DEPENDS_ON;LINK;HEADER_OUTPUT_DIR;PREFIX")
  set(_ARG_PREFIX ost)  
  parse_argument_list(_ARG "${_ARGS}" "NO_STATIC" ${ARGN})  
  if (NOT _ARG_NAME)
    message(FATAL_ERROR 
            "invalid use of module(): a module name must be provided")
  endif()

  if (ENABLE_STATIC AND _ARG_NO_STATIC)
    return()
  endif()
  if (_ARG_HEADER_OUTPUT_DIR)
    set(_HEADER_OUTPUT_DIR ${_ARG_HEADER_OUTPUT_DIR})
  else()
    set(_HEADER_OUTPUT_DIR "${_ARG_PREFIX}/${_ARG_NAME}")
  endif()
  set(_LIB_NAME ${_ARG_PREFIX}_${_ARG_NAME})
  string(TOUPPER ${_LIB_NAME} _UPPER_LIB_NAME)  
  #-----------------------------------------------------------------------------
  # create library  
  #-----------------------------------------------------------------------------
  file(MAKE_DIRECTORY ${LIB_STAGE_PATH})
  file(MAKE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
  file(MAKE_DIRECTORY ${LIBEXEC_STAGE_PATH})
  file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/tests")
  if (NOT TARGET create_stage)
    add_custom_target(create_stage COMMAND ${CMAKE_COMMAND} -E make_directory ${LIB_STAGE_PATH}
                                   COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
                                   COMMAND ${CMAKE_COMMAND} -E make_directory ${LIBEXEC_STAGE_PATH}
                                   COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/tests")
  endif()
  if (_ARG_SOURCES)
    # when there is at least one source file, we build a library
    set(_ABS_SOURCE_NAMES)
    foreach(_SOURCE ${_ARG_SOURCES})
      if (IS_ABSOLUTE ${_SOURCE})
        list(APPEND _ABS_SOURCE_NAMES "${_SOURCE}")
      else()
        list(APPEND _ABS_SOURCE_NAMES "${CMAKE_CURRENT_SOURCE_DIR}/${_SOURCE}")
      endif()
    endforeach()
    if (ENABLE_STATIC AND NOT _ARG_NO_STATIC)
      add_library(${_LIB_NAME} STATIC ${_ABS_SOURCE_NAMES})
    else()
      add_library(${_LIB_NAME} SHARED ${_ABS_SOURCE_NAMES})
    endif()
    set_target_properties(${_LIB_NAME} 
                          PROPERTIES OUTPUT_NAME ${_LIB_NAME}
                                     PROJECT_LABEL ${_ARG_NAME}
                                     EchoString   ${_ARG_NAME}
                                     MODULE_DEPS "${_ARG_DEPENDS_ON}")
    get_target_property(_DEFS ${_LIB_NAME} COMPILE_DEFINITIONS)
    add_dependencies(${_LIB_NAME} create_stage)
    set_target_properties(${_LIB_NAME} PROPERTIES
                          COMPILE_DEFINITIONS OST_MODULE_${_UPPER_LIB_NAME})
    set_target_properties(${_LIB_NAME} PROPERTIES
                          VERSION ${OST_VERSION_STRING}
                          SOVERSION ${OST_VERSION_MAJOR}.${OST_VERSION_MINOR})    
    set_target_properties(${_LIB_NAME} PROPERTIES
                          LIBRARY_OUTPUT_DIRECTORY ${LIB_STAGE_PATH}
                          ARCHIVE_OUTPUT_DIRECTORY ${LIB_STAGE_PATH}
                          RUNTIME_OUTPUT_DIRECTORY ${LIB_STAGE_PATH})
    if (APPLE)
      set_target_properties(${_LIB_NAME} PROPERTIES
                            LINK_FLAGS "-Wl,-rpath,@loader_path"
                            INSTALL_NAME_DIR "@rpath")
    endif()
    if (WIN32)
      install(TARGETS ${_LIB_NAME} RUNTIME DESTINATION bin)
    else()
      if (ENABLE_STATIC)
        install(TARGETS ${_LIB_NAME} ARCHIVE DESTINATION "${LIB_DIR}")
      else()
        install(TARGETS ${_LIB_NAME} LIBRARY DESTINATION "${LIB_DIR}")
      endif()
    endif()                          
    if (_ARG_LINK)
      target_link_libraries(${_LIB_NAME} ${_ARG_LINK})
    endif()
    foreach(_DEPENDENCY ${_ARG_DEPENDS_ON})
      target_link_libraries(${_LIB_NAME} ${_DEPENDENCY})
    endforeach()
    if (ENABLE_STATIC)
      target_link_libraries(${_LIB_NAME} ${STATIC_LIBRARIES})
    endif()
  else()
    add_custom_target("${_LIB_NAME}" ALL)
    set_target_properties("${_LIB_NAME}" PROPERTIES HEADER_ONLY 1 
                          MODULE_DEPS "${_ARG_DEPENDS_ON}")
  endif()
  #-----------------------------------------------------------------------------
  # stage headers  
  #-----------------------------------------------------------------------------
  if (_ARG_HEADERS)
    stage_and_install_headers("${_ARG_HEADERS}" "${_HEADER_OUTPUT_DIR}" "${_LIB_NAME}")
  endif()
endmacro()

#-------------------------------------------------------------------------------
# macro stage_and_install_headers
#-------------------------------------------------------------------------------
macro(stage_and_install_headers HEADERLIST HEADER_OUTPUT_DIR TARGET)
  add_custom_target("${TARGET}_headers" COMMENT "")
  add_dependencies("${TARGET}" "${TARGET}_headers")
  add_dependencies("${TARGET}_headers" create_stage)
  parse_file_list("${HEADERLIST}" _HEADER_MAP)
  map(KEYS _HEADER_MAP _HEADER_MAP_KEYS)
  foreach(_DIR ${_HEADER_MAP_KEYS})
    map(GET _HEADER_MAP ${_DIR} _HEADERS)
    set(_HDR_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${_DIR}")
    set(_ABS_HEADER_NAMES)
    foreach(_HDR ${_HEADERS})
      list(APPEND _ABS_HEADER_NAMES ${_HDR_SOURCE_DIR}/${_HDR})
    endforeach()
    set(_HDR_STAGE_DIR "${HEADER_OUTPUT_DIR}/${_DIR}")
    set(_FULL_HEADER_DIR "${HEADER_STAGE_PATH}/${_HDR_STAGE_DIR}")
    copy_if_different("" "${_FULL_HEADER_DIR}" "${_ABS_HEADER_NAMES}" "" "${TARGET}_headers")
    if(NOT WIN32)
      install(FILES ${_ABS_HEADER_NAMES} DESTINATION "include/${_HDR_STAGE_DIR}")
    endif(NOT WIN32)
  endforeach()
endmacro()


#-------------------------------------------------------------------------------
# Synopsis
#   executable(NAME exe_name SOURCES source1 source2 LINK link1 link2)
#
# Description:
#  Compile, link and stage a C++ executable
#-------------------------------------------------------------------------------
macro(executable)
  parse_argument_list(_ARG 
                      "NAME;SOURCES;LINK;DEPENDS_ON" "NO_RPATH;STATIC" ${ARGN})
  if (NOT _ARG_NAME)
    message(FATAL_ERROR "invalid use of executable(): a name must be provided")
  endif()
  add_executable(${_ARG_NAME} ${_ARG_SOURCES})
  if (APPLE AND NOT _ARG_NO_RPATH AND NOT ENABLE_STATIC)
    set_target_properties(${_ARG_NAME} PROPERTIES
                          LINK_FLAGS "-Wl,-rpath,@loader_path/../lib/")
  endif()
  if (_ARG_LINK)
    target_link_libraries(${_ARG_NAME} ${_ARG_LINK})
  endif()
  foreach(_DEP ${_ARG_DEPENDS_ON})
    target_link_libraries(${_ARG_NAME} ${_DEP})
  endforeach()
  if (ENABLE_STATIC AND _ARG_STATIC)
    target_link_libraries(${_ARG_NAME} ${STATIC_LIBRARIES})
    if (UNIX AND NOT APPLE)
      set_target_properties(${_ARG_NAME} PROPERTIES LINK_SEARCH_START_STATIC TRUE)
      set_target_properties(${_ARG_NAME} PROPERTIES LINK_SEARCH_END_STATIC TRUE)
      if (OST_GCC_LESS_45)
        set_target_properties(${_ARG_NAME} PROPERTIES LINK_FLAGS
                              "-static-libgcc -static -pthread")
      else()
        set_target_properties(${_ARG_NAME} PROPERTIES LINK_FLAGS
                              "-static-libgcc -static-libstdc++ -static -pthread")
      endif()
    endif()
  endif()
  install(TARGETS ${_ARG_NAME} DESTINATION bin)
endmacro()


#-------------------------------------------------------------------------------
# Synopsis
#   executable_libexec(NAME exe_name SOURCES source1 source2 LINK link1 link2)
#
# Description:
#  Compile, link and stage a C++ executable into the libexec directory
#-------------------------------------------------------------------------------
macro(executable_libexec)
  parse_argument_list(_ARG 
                      "NAME;SOURCES;LINK;DEPENDS_ON" "NO_RPATH;STATIC" ${ARGN})
  if (NOT _ARG_NAME)
    message(FATAL_ERROR "invalid use of executable(): a name must be provided")
  endif()   
  add_executable(${_ARG_NAME} ${_ARG_SOURCES})
  set_target_properties(${_ARG_NAME}
                        PROPERTIES RUNTIME_OUTPUT_DIRECTORY
                       "${LIBEXEC_STAGE_PATH}")  
  set_target_properties(${_ARG_NAME}
                        PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE
                       "${LIBEXEC_STAGE_PATH}")  
  set_target_properties(${_ARG_NAME}
                        PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG
                       "${LIBEXEC_STAGE_PATH}")  
  if (NOT _ARG_NO_RPATH AND NOT _ARG_STATIC)
    if (APPLE)
      set_target_properties(${_ARG_NAME} PROPERTIES
                            LINK_FLAGS "-Wl,-rpath,@loader_path/../../lib")
    elseif (UNIX)
      set_target_properties(${_ARG_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/../../${LIB_DIR}")
    endif (APPLE)
  endif (NOT _ARG_NO_RPATH AND NOT _ARG_STATIC)
  if (_ARG_LINK)
    target_link_libraries(${_ARG_NAME} ${_ARG_LINK})
  endif()
  if (ENABLE_STATIC AND _ARG_STATIC)
    target_link_libraries(${_ARG_NAME} ${STATIC_LIBRARIES})
    set_target_properties(${_ARG_NAME}
                          PROPERTIES LINK_SEARCH_END_STATIC TRUE)  

  endif()
  foreach(_DEP ${_ARG_DEPENDS_ON})
    target_link_libraries(${_ARG_NAME} ${_DEP})
  endforeach()
  install(TARGETS ${_ARG_NAME} DESTINATION ${LIBEXEC_PATH})
endmacro()

#-------------------------------------------------------------------------------
# Synopsis:
#   substitute(IN_FILE in_file OUT_FILE out_file DICT a=b c=d)
#
#-------------------------------------------------------------------------------
macro(substitute)
 parse_argument_list(_ARG 
                     "IN_FILE;OUT_FILE;DICT" "" ${ARGN})
  if (NOT _ARG_IN_FILE)
    message(FATAL_ERROR "invalid use of substitute(): no IN_FILE given")
  endif()
  if (NOT _ARG_OUT_FILE)
    message(FATAL_ERROR "invalid use of substitute(): no OUT_FILE given")
  endif()
  set(_SUBST_DICT -DINPUT_FILE=${_ARG_IN_FILE} -DOUT_FILE=${_ARG_OUT_FILE})
  foreach(_SUBST ${_ARG_DICT})
    list(APPEND _SUBST_DICT -D${_SUBST})
  endforeach()
  add_custom_target(subst_${_ARG_OUT_FILE} ALL COMMAND 
                    ${CMAKE_COMMAND} ${_SUBST_DICT}
                    -P ${CMAKE_SOURCE_DIR}/cmake_support/substitute.cmake)
endmacro()

#-------------------------------------------------------------------------------
# Synopsis:
#   script(NAME script_name INPUT input_name SUBSTITUTE key=val key=val
#          [OUTPUT_DIR dir] [TARGET target])
#-------------------------------------------------------------------------------
macro(script)
  set(_ARG_OUTPUT_DIR bin)
  parse_argument_list(_ARG 
                      "NAME;INPUT;SUBSTITUTE;TARGET;OUTPUT_DIR" "" ${ARGN})
  if (NOT _ARG_NAME)
    message(FATAL_ERROR "invalid use of executable(): a name must be provided")
  endif()
  set(_INPUT ${_ARG_NAME})
  if (_ARG_INPUT)
    set(_INPUT ${_ARG_INPUT})
  endif()
  if (_ARG_SUBSTITUTE)
    if (NOT _ARG_INPUT)
      message(FATAL_ERROR "script() can only substitute when INPUT is present.")    
    endif()

    substitute(IN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${_INPUT}" OUT_FILE ${_ARG_NAME} 
               DICT ${_ARG_SUBSTITUTE})
  endif()
  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${_ARG_NAME} DESTINATION ${_ARG_OUTPUT_DIR} 
          PERMISSIONS WORLD_EXECUTE GROUP_EXECUTE OWNER_EXECUTE 
                      WORLD_READ GROUP_READ OWNER_READ)
  copy_if_different("./" "${STAGE_DIR}/${_ARG_OUTPUT_DIR}" 
                    "${_ARG_NAME}" "TARGETS" ${_ARG_TARGET})
  add_dependencies(${_ARG_TARGET} subst_${_ARG_NAME})
endmacro()

#-------------------------------------------------------------------------------
# Synopsis:
#   ui_to_python(libname stagedir[input_file1 ...])
# Description:
#   Calls pyuic on every input file. The resulting python files are stored in
#   the variable with name out_files.
#-------------------------------------------------------------------------------
macro(ui_to_python LIBNAME PYMODDIR STAGEDIR)
  set(_input_files ${ARGN})
  add_custom_target("${LIBNAME}_ui" ALL)
  add_dependencies("_${LIBNAME}" "${LIBNAME}_ui")
  find_program(_PYUIC_EXECUTABLE
    NAMES pyuic4-${PYTHON_VERSION} pyuic4 pyuic pyuic4.bat
    PATHS  ENV PATH 
  )  
  if(NOT _PYUIC_EXECUTABLE)
    message(FATAL_ERROR "Could not find pyuic command in " ${QT_BINARY_DIR} " for python version " ${PYTHON_VERSION})
  endif(NOT _PYUIC_EXECUTABLE)
  set(out_files)
  foreach(input_file ${_input_files})
    get_filename_component(_out_file ${input_file} NAME_WE)
    get_filename_component(_in_file ${input_file} ABSOLUTE)
    set(_out_file ${_out_file}_ui.py)
    set(_abs_out_file ${STAGEDIR}/${_out_file})
    add_custom_command(TARGET ${LIBNAME}_ui
                       COMMAND ${_PYUIC_EXECUTABLE} -o ${_abs_out_file} ${_in_file}
                       VERBATIM DEPENDS ${input_file}
                       )
    list(APPEND out_files ${_abs_out_file})
  endforeach()
  compile_py_files(_${LIBNAME} ${STAGEDIR} compiled_files ${out_files})
  install(FILES ${out_files} DESTINATION "${LIB_DIR}/${PYMODDIR}")
  install(FILES ${compiled_files} DESTINATION "${LIB_DIR}/${PYMODDIR}")
endmacro()

#-------------------------------------------------------------------------------
# Synopsis:
#   compile_py_files(module out_dir compiled_files [input_file1 ...])
# Description:
#   Calls pyuic on every input file. The resulting python files are stored in
#   the variable with name compiled_files.
#-------------------------------------------------------------------------------
macro(compile_py_files module out_dir compiled_files_name)
  set(_input_files ${ARGN})
  set(${compiled_files_name})
  foreach(input_file ${_input_files})
    get_filename_component(_out_file ${input_file} NAME_WE)
    get_filename_component(_in_file ${input_file} ABSOLUTE)
    set(_out_file ${out_dir}/${_out_file}.pyc)
    list(APPEND ${compiled_files_name} ${_out_file})
    get_filename_component(_in_name ${input_file} NAME)
    file(MAKE_DIRECTORY  ${out_dir})
    add_custom_command(TARGET ${module}
                       COMMAND ${PYTHON_BINARY} -c "import py_compile;py_compile.compile(\"${_in_file}\",\"${_out_file}\",\"${_in_name}\",doraise=True)"
                       VERBATIM DEPENDS ${input_file}
                       )
  endforeach()
endmacro()

#-------------------------------------------------------------------------------
# Synopsis:
#   pymod(NAME name CPP source1 source2 PY source source2 [IN_DIR dir] 
#         source3 source4 [IN_DIR dir] [LINK link] [OUTPUT_DIR dir] [UI user_interface_files])
#
# Description:
#  Define a python module consisting of C++ type wrappers and/or code written in 
#  Python.
# NAME is the name of
#-------------------------------------------------------------------------------
macro(pymod)
  #-----------------------------------------------------------------------------
  # deal with arguments
  #-----------------------------------------------------------------------------
  set(_ARG_PREFIX ost)
  parse_argument_list(_ARG 
                      "NAME;CPP;PY;LINK;OUTPUT_DIR;UI;PREFIX" "" ${ARGN})
  if (NOT _ARG_NAME)
    message(FATAL_ERROR "invalid use of pymod(): a name must be provided")
  endif()
  if (ENABLE_STATIC)
    return()
  endif()
  if (_ARG_OUTPUT_DIR)
    set(PYMOD_DIR "python${PYTHON_VERSION}/site-packages/${_ARG_OUTPUT_DIR}")
  else()
    set(PYMOD_DIR "python${PYTHON_VERSION}/site-packages/${_ARG_PREFIX}/${_ARG_NAME}")
  endif()
  set(_LIB_NAME ${_ARG_PREFIX}_${_ARG_NAME})
  set(PYMOD_STAGE_DIR "${LIB_STAGE_PATH}/${PYMOD_DIR}")
  file(MAKE_DIRECTORY ${PYMOD_STAGE_DIR})
  include_directories(${PYTHON_INCLUDE_PATH})
  #-----------------------------------------------------------------------------
  # compile and link C++ wrappers
  #-----------------------------------------------------------------------------
  if (_ARG_CPP)
    add_library("_${_LIB_NAME}" MODULE ${_ARG_CPP})
    set_target_properties("_${_LIB_NAME}"
                          PROPERTIES ECHO_STRING
                          "Building Python Module ${_ARG_NAME}")
    if (_ARG_PREFIX)
      set(_PARENT_NAME "${_ARG_PREFIX}_${_ARG_NAME}")
    else()
      set(_PARENT_NAME "${_ARG_NAME}")
    endif()
    get_target_property(_CUSTOM_CHECK "${_PARENT_NAME}" HEADER_ONLY)
    if (NOT _CUSTOM_CHECK)
      set(_PARENT_LIB_NAME "${_PARENT_NAME}")
    endif()
    target_link_libraries("_${_LIB_NAME}" ${_PARENT_LIB_NAME} 
                          ${PYTHON_LIBRARIES} ${BOOST_PYTHON_LIBRARIES})

    set_target_properties("_${_LIB_NAME}"
                          PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PYMOD_STAGE_DIR})
    set_target_properties("_${_LIB_NAME}"
                          PROPERTIES LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PYMOD_STAGE_DIR})
    set_target_properties("_${_LIB_NAME}"
                          PROPERTIES LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PYMOD_STAGE_DIR})

    if (NOT ENABLE_STATIC)
      if (_USE_RPATH)
        string(REGEX REPLACE "/[^/]*" "/.." inv_pymod_path "/${PYMOD_DIR}")
        set_target_properties("_${_LIB_NAME}"
                              PROPERTIES INSTALL_RPATH "$ORIGIN${inv_pymod_path}/")
      else()
        set_target_properties("_${_LIB_NAME}"
                              PROPERTIES INSTALL_RPATH "")
      endif()
    endif()
    if (APPLE)
      file(RELATIVE_PATH _REL_PATH "${PYMOD_STAGE_DIR}" "${LIB_STAGE_PATH}")
      set_target_properties("_${_LIB_NAME}" PROPERTIES
                            LINK_FLAGS "-Wl,-rpath,@loader_path/${_REL_PATH}"
                            INSTALL_NAME_DIR "@rpath")
    endif()                          
    if (NOT WIN32)
      set_target_properties("_${_LIB_NAME}"
                          PROPERTIES PREFIX "")
    else ()
      set_target_properties("_${_LIB_NAME}"
                          PROPERTIES SUFFIX ".pyd")
    endif()
    install(TARGETS "_${_LIB_NAME}" LIBRARY DESTINATION
            "${LIB_DIR}/${PYMOD_DIR}")
  else()
    add_custom_target("_${_LIB_NAME}" ALL)
  endif()
  #-----------------------------------------------------------------------------
  # build ui files
  #-----------------------------------------------------------------------------
  if (_ARG_UI)
    ui_to_python(${_LIB_NAME} ${PYMOD_DIR} ${PYMOD_STAGE_DIR} ${_ARG_UI})
  endif()  
  #-----------------------------------------------------------------------------
  # compile python files
  #-----------------------------------------------------------------------------
  if (_ARG_PY)
    parse_file_list("${_ARG_PY}" _PYFILE_MAP)
    map(KEYS _PYFILE_MAP _PYFILE_MAP_KEYS)
    foreach(_DIR ${_PYFILE_MAP_KEYS})
      map(GET _PYFILE_MAP ${_DIR} _PY_FILES)
      set(_ABS_PY_FILES)
      set(_PY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${_DIR}")
      foreach(_PY ${_PY_FILES})
        list(APPEND _ABS_PY_FILES "${_PY_SOURCE_DIR}/${_PY}")
      endforeach()
      install(FILES ${_ABS_PY_FILES} DESTINATION "${LIB_DIR}/${PYMOD_DIR}/${_DIR}")
      string(REPLACE "/" "_" _DIR_NO_SLASH "${_DIR}")
      set(_PYMOD_TARGET "${_LIB_NAME}_${_DIR_NO_SLASH}_pymod")
      string(REPLACE "_." "" _PYMOD_TARGET "${_PYMOD_TARGET}")
      add_custom_target(${_PYMOD_TARGET} ALL)
      copy_if_different("./" "${PYMOD_STAGE_DIR}/${_DIR}"
                        "${_ABS_PY_FILES}" "TARGETS"
                        "${_PYMOD_TARGET}")
      compile_py_files(_${_LIB_NAME} ${PYMOD_STAGE_DIR}/${_DIR} compiled_files ${_ABS_PY_FILES})
      install(FILES ${compiled_files} DESTINATION "${LIB_DIR}/${PYMOD_DIR}/${_DIR}")
    endforeach()
  endif()  
  get_target_property(_MOD_DEPS "${_PARENT_NAME}" MODULE_DEPS)
  if(_MOD_DEPS)
    foreach(dep ${_MOD_DEPS})
       add_dependencies("_${_LIB_NAME}" "_${dep}")
    endforeach()
  endif()
endmacro()

add_custom_target(check)
add_custom_target(check_xml)
if (WIN32)
  set_target_properties(check PROPERTIES EXCLUDE_FROM_ALL "1")
  set_target_properties(check_xml PROPERTIES EXCLUDE_FROM_ALL "1")
endif()

#-------------------------------------------------------------------------------
# ost_unittest
#
# define a unit test
#-------------------------------------------------------------------------------
macro(ost_unittest)
  set(_ARG_PREFIX ost)
  parse_argument_list(_ARG 
                      "MODULE;PREFIX;SOURCES;LINK" "" ${ARGN})
    set(_SOURCES ${_ARG_SOURCES})
    set(CPP_TESTS)
    set(PY_TESTS)
    set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}/tests")
    foreach(src ${_SOURCES})
      if (${src} MATCHES "\\.py$")
       list(APPEND PY_TESTS "${src}")      
      else()
        list(APPEND CPP_TESTS "${CMAKE_CURRENT_SOURCE_DIR}/${src}")
     endif()
    endforeach()
    set(_SOURCES ${CPP_TESTS})
    set(_test_name "${_ARG_PREFIX}_${_ARG_MODULE}_tests")
    if(DEFINED CPP_TESTS)
      if(COMPILE_TESTS)
        add_executable(${_test_name} ${_SOURCES})
      else()
        add_executable(${_test_name} EXCLUDE_FROM_ALL ${_SOURCES})
      endif()
      set_target_properties(${_test_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests"  )
      set_target_properties(${_test_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests"  )
      set_target_properties(${_test_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests"  )

      target_link_libraries(${_test_name} ${BOOST_UNIT_TEST_LIBRARIES} "${_ARG_PREFIX}_${_ARG_MODULE}")
      if (WIN32)
        set(TEST_COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${_test_name}.exe || echo)
        message(${TEST_COMMAND})
        set(ENV{PP} ${STAGE_DIR}/bin)
        add_custom_target("${_test_name}_run" 
                        COMMAND echo %PP%
                        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                        COMMENT "running checks for module ${_ARG_MODULE}"
                        DEPENDS ${_test_name})
        add_test("${_test_name}" ${CMAKE_CURRENT_BINARY_DIR}/${_test_name}.exe)
        set_target_properties("${_test_name}_run" PROPERTIES EXCLUDE_FROM_ALL "1")
        add_dependencies(check "${_test_name}_run")
      else()
        add_custom_target("${_test_name}_run"
                        COMMAND OST_ROOT=${STAGE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${_test_name}
                        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                        COMMENT "running checks for module ${_ARG_MODULE}"
                        DEPENDS ${_test_name})
        add_custom_target("${_test_name}_run_xml"
                        COMMAND OST_ROOT=${STAGE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${_test_name} --log_format=xml --log_level=all > ${_test_name}_log.xml || echo
                        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                        COMMENT "running checks for module ${_ARG_MODULE}"
                        DEPENDS ${_test_name})
        add_test("${_test_name}" ${CMAKE_CURRENT_BINARY_DIR}/${_test_name} )
        add_dependencies(check_xml "${_test_name}_run_xml")
        add_dependencies(check "${_test_name}_run")
      endif()
      
      if (_ARG_LINK)
        target_link_libraries("${_test_name}" ${_ARG_LINK})
      endif()

      set_target_properties(${_test_name}
                            PROPERTIES RUNTIME_OUTPUT_DIRECTORY
                            "${CMAKE_CURRENT_BINARY_DIR}")
    endif()

    foreach(py_test ${PY_TESTS})
      set(python_path $ENV{PYTHONPATH})
      if(python_path)
        set(python_path ":${python_path}")
      endif(python_path)
      set(python_path "${LIB_STAGE_PATH}/python${PYTHON_VERSION}/site-packages${python_path}")
      if(WIN32)
        set (PY_TESTS_CMD "PYTHONPATH=${python_path}  ${PYTHON_BINARY}")
        # todo fix python unit test running for Windows
        #set (PY_TESTS_CMD "${EXECUTABLE_OUTPUT_PATH}/ost.bat")
        #add_custom_target("${py_test}_run"
        #          CALL "${PY_TESTS_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/${py_test} || echo"
        #          WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        #          COMMENT "running checks ${py_test}" VERBATIM)
        #set_target_properties("${py_test}_run" PROPERTIES EXCLUDE_FROM_ALL "1")
        #add_dependencies("${py_test}_run" ost_scripts "_${_ARG_PREFIX}_${_ARG_MODULE}")
        #add_dependencies(check "${py_test}_run")
      else()
        set (PY_TESTS_CMD "PYTHONPATH=${python_path}  ${PYTHON_BINARY}")
        add_custom_target("${py_test}_run"
                  sh -c "${PY_TESTS_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/${py_test}"
                  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                  COMMENT "running checks ${py_test}" VERBATIM)
        add_custom_target("${py_test}_run_xml"
                  sh -c "${PY_TESTS_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/${py_test} xml || echo"
                  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                  COMMENT "running checks ${py_test}" VERBATIM)
        add_dependencies("${py_test}_run_xml" ost_scripts "_${_ARG_PREFIX}_${_ARG_MODULE}")
        add_dependencies("${py_test}_run" ost_scripts "_${_ARG_PREFIX}_${_ARG_MODULE}")
        add_dependencies(check "${py_test}_run")
        add_dependencies(check_xml "${py_test}_run_xml")
      endif()
      
    endforeach()
    
    # get_target_property(OUTT ${_test_name} GENERATOR_FILE_NAME)
    # message("${OUTT}")
    
    # get_target_property(OUTT check GENERATOR_FILE_NAME)
    # message("${OUTT}")
endmacro()

#-------------------------------------------------------------------------------
# make sure the previously detected Python interpreter has the given module
#-------------------------------------------------------------------------------
macro(ost_find_python_module MODULE)
  if (NOT PYTHON_MODULE_${MODULE})
    message(STATUS "Searching for python module ${MODULE} for ${PYTHON_BINARY}")
    execute_process(COMMAND ${PYTHON_BINARY} -c "import ${MODULE}"
                    OUTPUT_QUIET ERROR_QUIET
                    RESULT_VARIABLE _IMPORT_ERROR)
    if (_IMPORT_ERROR)
      message(FATAL_ERROR "Could not find python module ${MODULE}. Please install it")
    else()
      message(STATUS "Found python module ${MODULE}")
      set("PYTHON_MODULE_${MODULE}" FOUND CACHE STRING "" FORCE)
    endif()
  endif()
endmacro()

#-------------------------------------------------------------------------------
# make sure the previously detected Python interpreter has the given module
# while allowing alternative module names
#-------------------------------------------------------------------------------
macro(ost_find_python_module_alt MODULES)
  set(_PY_MODS "")
  foreach(py_mod ${MODULES})
    if (NOT PYTHON_MODULE_${py_mod})
      set(_PY_MODS "${_PY_MODS} ${py_mod}")
      message(STATUS "Searching for python module ${py_mod} for ${PYTHON_BINARY}")
      execute_process(COMMAND ${PYTHON_BINARY} -c "import ${py_mod}"
                      OUTPUT_QUIET ERROR_QUIET
                      RESULT_VARIABLE _IMPORT_ERROR)
      if (NOT _IMPORT_ERROR)
        message(STATUS "Found python module ${py_mod}")
        set("PYTHON_MODULE_${py_mod}" FOUND CACHE STRING "" FORCE)
        break()
      endif (NOT _IMPORT_ERROR)
    else ()
      message(STATUS "Already found python module ${py_mod}")
      unset(_IMPORT_ERROR)
      break()
    endif (NOT PYTHON_MODULE_${py_mod})
  endforeach(py_mod ${MODULES})
  if (_IMPORT_ERROR)
    message(FATAL_ERROR "Could not find one of python modules ${_PY_MODS}. Please install one of them.")
  endif ()    
endmacro()

#-------------------------------------------------------------------------------
# this macro tries to detect a very common problem during configuration stage:
# it makes sure that boost python is linked against the same version of python 
# that we are linking against. 
#-------------------------------------------------------------------------------
macro(ost_match_boost_python_version)
  include(CheckCXXSourceRuns)
  # this variable may either be a simple library path or list that contains
  # different libraries for different build-options. For example:
  # optimized;<lib1>;debug;<lib2>
  set(_BOOST_PYTHON_LIBRARY ${BOOST_PYTHON_LIBRARIES})
  list(LENGTH _BOOST_PYTHON_LIBRARY _BP_LENGTH)
  if (_BP_LENGTH GREATER 1)
    list(FIND _BOOST_PYTHON_LIBRARY optimized _OPTIMIZED_INDEX)
    if (_OPTIMIZED_INDEX EQUAL -1)
      message(FATAL_ERROR 
              "Error while trying to get path of boost python library")
    endif()
    math(EXPR _LIB_INDEX 1+${_OPTIMIZED_INDEX})
    list(GET _BOOST_PYTHON_LIBRARY ${_LIB_INDEX} _BP_LIB_PATH)
    set(_BOOST_PYTHON_LIBRARY ${_BP_LIB_PATH})
  endif()
  set(CMAKE_REQUIRED_FLAGS "-I${PYTHON_INCLUDE_PATH} -I${Boost_INCLUDE_DIR} ${PYTHON_LIBRARIES} ${_BOOST_PYTHON_LIBRARY}")
  check_cxx_source_runs(
"#include <boost/python.hpp>

using namespace boost::python;
                           
int main( int argc, char ** argv ) {
  try {
    Py_Initialize();

    object main_module((
      handle<>(borrowed(PyImport_AddModule(\"__main__\")))));

    object main_namespace = main_module.attr(\"__dict__\");

    handle<> ignored(( PyRun_String( \"print 'Hello, World'\",
                                     Py_file_input,
                                     main_namespace.ptr(),
                                     main_namespace.ptr() ) ));
  } catch( error_already_set ) {
    PyErr_Print();
    return 1;
  }   
  return 0;
}" PYTHON_VERSION_FOUND_MATCHES_PYTHON_VERSION_BOOST_WAS_COMPILED_WITH)

  if(NOT PYTHON_VERSION_FOUND_MATCHES_PYTHON_VERSION_BOOST_WAS_COMPILED_WITH)
     message(FATAL_ERROR "Python versions mismatch!\n"
             "Make sure the python version for boost match the one you "
             "specified during configuration\n")
  endif()
endmacro()


#-------------------------------------------------------------------------------
# this macro sets up the stage directories
#-------------------------------------------------------------------------------
macro(setup_stage)
  set(STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${STAGE_DIR}/bin  )
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${STAGE_DIR}/bin  )
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${STAGE_DIR}/bin  )
  set(HEADER_STAGE_PATH ${STAGE_DIR}/include )
  set(SHARED_DATA_PATH ${STAGE_DIR}/share/openstructure  )

  if (WIN32)
    set(ENV{PATH} ${STAGE_DIR}/bin\;$ENV{PATH})
  endif (WIN32)
  if (UNIX AND NOT APPLE)
    check_architecture()
  endif()
  set (ARCH ${CMAKE_NATIVE_ARCH})
  if ("${ARCH}" MATCHES "64" AND NOT _UBUNTU_LAYOUT)
    set(LIB_DIR lib64  )
    set(LIB_STAGE_PATH "${STAGE_DIR}/lib64"  )
  else()
    set(LIB_DIR lib  )
    set(LIB_STAGE_PATH "${STAGE_DIR}/lib"  )
  endif()
  if (_UBUNTU_LAYOUT)
    set(LIBEXEC_PATH ${LIB_DIR}/openstructure/libexec  )
    set(LIBEXEC_STAGE_PATH ${LIB_STAGE_PATH}/openstructure/libexec  )
  else()
    set(LIBEXEC_PATH libexec/openstructure  )
    set(LIBEXEC_STAGE_PATH ${STAGE_DIR}/libexec/openstructure  )
  endif()
 
  include_directories("${HEADER_STAGE_PATH}")
  link_directories(${LIB_STAGE_PATH})

endmacro()

#-------------------------------------------------------------------------------
# get compiler version
#-------------------------------------------------------------------------------
function(get_compiler_version _OUTPUT_VERSION)
  exec_program(${CMAKE_CXX_COMPILER}
               ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpfullversion -dumpversion
               OUTPUT_VARIABLE _COMPILER_VERSION
  )
  string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2"
    _COMPILER_VERSION ${_COMPILER_VERSION})

  set(${_OUTPUT_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE)
endfunction()



macro(setup_compiler_flags)
  if (WIN32)
     # add_definitions(-DBOOST_TEST_INCLUDED)

     add_definitions(-D_USE_MATH_DEFINES -D_CRT_SECURE_NO_DEPRECATE
                     -D_SCL_SECURE_NO_DEPRECATE -DNOMINMAX)
     #add_definitions(-Zc:wchar_t-)   #
    # add_definitions(-MDd -vmg -EHsc -GR)
    #GR:Uses the __fastcall calling convention (x86 only).
    #-EHsc to specify the synchronous exception handling mode/
    #-vmg Uses full generality for pointers to members.
    #add_definitions(-DBOOST_ZLIB_BINARY=zdll)
    #add_definitions(-NODEFAULTLIB:LIBCMTD.lib)
  endif()


  if (CMAKE_COMPILER_IS_GNUCXX)
    get_compiler_version(_GCC_VERSION)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" )
    if (_GCC_VERSION MATCHES "44")
      # gcc 4.4. is very strict about aliasing rules. the shared_count
      # implementation that is used boost's shared_ptr violates these rules. To
      # silence the warnings and prevent miscompiles, enable
      #  -fno-strict-aliasing
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing" )
    endif()
    #message(STATUS "GCC VERSION " ${_GCC_VERSION})
    if (_GCC_VERSION LESS "60")
      # for older compilers we need to enable C++11 for Qt5
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
    endif()
    if (_GCC_VERSION LESS "45")
      set(OST_GCC_LESS_45 true)
    else()
      set(OST_GCC_LESS_45 false)
    endif()
  endif()
endmacro()
set(_BOOST_MIN_VERSION 1.31)

macro(setup_boost)
  set (Boost_NO_BOOST_CMAKE TRUE)
  # starting with CMake 3.11 we could use the following instead of the foreach
  # find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS
  #              python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR} REQUIRED)
  # set(BOOST_PYTHON_LIBRARIES ${Boost_LIBRARIES})
  # see https://cmake.org/cmake/help/v3.11/module/FindBoost.html
  foreach(_python_lib_name python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
                           python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}
                           python${PYTHON_VERSION_MAJOR}
                           python)
    find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS ${_python_lib_name} QUIET)
    if(Boost_FOUND)
      message(STATUS "Found Boost package: " ${_python_lib_name})
      set(BOOST_PYTHON_LIBRARIES ${Boost_LIBRARIES})
      break()
    else()
      message(STATUS "Boost package not found: " ${_python_lib_name}
                     ". Trying alternative names!")
    endif()
  endforeach(_python_lib_name)
  if(NOT BOOST_PYTHON_LIBRARIES)
    message(FATAL_ERROR "Failed to find any Boost Python library!")
  endif()
  set(Boost_LIBRARIES)
  find_package(Boost ${_BOOST_MIN_VERSION}
               COMPONENTS unit_test_framework REQUIRED)
  set(BOOST_UNIT_TEST_LIBRARIES ${Boost_LIBRARIES})
  set(Boost_LIBRARIES)
  if (ENABLE_STATIC)
    set(Boost_USE_STATIC_LIBS ON)
  endif()
  find_package(Boost ${_BOOST_MIN_VERSION}
               COMPONENTS filesystem system REQUIRED)
  set(BOOST_LIBRARIES ${Boost_LIBRARIES})
  set(Boost_LIBRARIES)
  find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS iostreams REQUIRED)
  set(BOOST_IOSTREAM_LIBRARIES ${Boost_LIBRARIES})
  set(Boost_LIBRARIES)
  find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS program_options REQUIRED)
  set(BOOST_PROGRAM_OPTIONS ${Boost_LIBRARIES})
  set(Boost_LIBRARIES)
  find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS regex REQUIRED)
  set(BOOST_REGEX_LIBRARIES ${Boost_LIBRARIES})
  set(Boost_LIBRARIES)
  find_package(Boost ${_BOOST_MIN_VERSION} COMPONENTS thread REQUIRED)
  set(BOOST_THREAD ${Boost_LIBRARIES})
  set(Boost_LIBRARIES)
endmacro()


#-------------------------------------------------------------------------------
# Synopsis:
#   ost_action_init()
#
# Description:
#   Initialise cached variables
#-------------------------------------------------------------------------------
macro(ost_action_init)
  set(OST_ACTION_NAMES "" CACHE INTERNAL "" FORCE)
endmacro(ost_action_init)

#-------------------------------------------------------------------------------
# Synopsis:
#   ost_action(ACTION TARGET)
#
# Description:
#   Add a script to actions.
#   ACTION script to be added (needs to have permissions to be executed)
#   TARGET make target to add the action to
#-------------------------------------------------------------------------------
macro(ost_action ACTION TARGET)
  copy_if_different("${CMAKE_CURRENT_SOURCE_DIR}"
                    "${STAGE_DIR}/${LIBEXEC_PATH}" 
                    "${ACTION}" "TARGETS" ${TARGET})
  install(FILES "${ACTION}" DESTINATION "${LIBEXEC_PATH}"
          PERMISSIONS WORLD_EXECUTE GROUP_EXECUTE OWNER_EXECUTE 
                      WORLD_READ GROUP_READ OWNER_READ)
  # storing tool names for bash completion
  string(REGEX REPLACE "^ost-" "" stripped_action ${ACTION})
  if(DEFINED OST_ACTION_NAMES)
    if(${OST_ACTION_NAMES} MATCHES "${stripped_action}")
      set(_ACTION_NAMES "${OST_ACTION_NAMES}")
    else()
      if("${OST_ACTION_NAMES}" STREQUAL "")
        set(_ACTION_NAMES "${stripped_action}")
      else()
        set(_ACTION_NAMES "${OST_ACTION_NAMES} ${stripped_action}")
      endif()
    endif()
  else()
    set(_ACTION_NAMES "${stripped_action}")
  endif()
  set(OST_ACTION_NAMES "${_ACTION_NAMES}" CACHE INTERNAL "" FORCE)
endmacro(ost_action)