From 6267dc4011b446c558748bda8fbc4a83cc1c98b2 Mon Sep 17 00:00:00 2001
From: Andreas Schenk <andreas_schenk@hms.harvard.edu>
Date: Fri, 7 Sep 2012 15:19:07 -0400
Subject: [PATCH] added copy_dependenies.cmake, which allows dependency
 resolution within cmake added CPack configuration to create complete bundle
 dmg on OSX with "make package"

---
 CMakeLists.txt                       |   5 +-
 cmake_support/CopyDependencies.cmake | 619 +++++++++++++++++++++++++++
 cmake_support/Deployment.cmake       | 102 +++--
 deployment/macos/Info.plist          |  35 ++
 4 files changed, 727 insertions(+), 34 deletions(-)
 create mode 100644 cmake_support/CopyDependencies.cmake
 create mode 100644 deployment/macos/Info.plist

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7f557d7d2..69a37ae1b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -291,9 +291,12 @@ endif()
 
 add_subdirectory(modules)
 add_subdirectory(scripts)
+add_subdirectory(tools)
+# deployment has to come last, to ensure that all install commands are run before deployment
+# magic is done
 add_subdirectory(deployment)
+
 set(FILES_TO_BE_REMOVED ${CMAKE_SOURCE_DIR}/stage tests)
-add_subdirectory(tools)
 set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
                              "${FILES_TO_BE_REMOVED}")
 
diff --git a/cmake_support/CopyDependencies.cmake b/cmake_support/CopyDependencies.cmake
new file mode 100644
index 000000000..1f522a2ec
--- /dev/null
+++ b/cmake_support/CopyDependencies.cmake
@@ -0,0 +1,619 @@
+#-------------------------------------------------------------------------------
+#  Author:  Andreas Schenk
+#
+#  This file contains functions to determine, copy and verify the dependencies
+#  of the application bundle.
+#
+#  Part of this code was taken from BundleUtilities.cmake, distributed
+#  under the OSI-approved BSD License (see below), and adapted.
+#-------------------------------------------------------------------------------
+
+#  CMake - Cross Platform Makefile Generator
+#  Copyright 2000-2011 Kitware, Inc., Insight Software Consortium
+#  All rights reserved.
+#  
+#  Redistribution and use in source and binary forms, with or without
+#  modification, are permitted provided that the following conditions
+#  are met:
+#  
+#  * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#  
+#  * Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#  
+#  * Neither the names of Kitware, Inc., the Insight Software Consortium,
+#    nor the names of their contributors may be used to endorse or promote
+#    products derived from this software without specific prior written
+#    permission.
+#  
+#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+#  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#=============================================================================
+# function copy dependencies
+#=============================================================================
+function(copy_dependencies binaries)
+  #--------------------------------------------------------------------------
+  message("copy dependencies: determining dependencies")
+  #--------------------------------------------------------------------------
+  message(STATUS "  binaries='${binaries}'")
+  collect_dependencies("${binaries}" keys)
+
+  #--------------------------------------------------------------------------
+  message("copy dependencies: copying files")
+  #--------------------------------------------------------------------------
+  list(LENGTH keys n)
+  math(EXPR n ${n}*2)
+
+  set(i 0)
+  foreach(key ${keys})
+    math(EXPR i ${i}+1)
+    if(${${key}_COPYFLAG})
+      message(STATUS "${i}/${n}: copying '${${key}_RESOLVED_ITEM}' to '${${key}_RESOLVED_EMBEDDED_ITEM}'")
+      set(item "${${key}_ITEM}")
+      copy_resolved_item("${${key}_RESOLVED_ITEM}" "${${key}_RESOLVED_EMBEDDED_ITEM}")
+    else(${${key}_COPYFLAG})
+      message(STATUS "${i}/${n}: *NOT* copying '${${key}_RESOLVED_ITEM}'")
+    endif(${${key}_COPYFLAG})
+  endforeach(key)
+
+  #--------------------------------------------------------------------------
+  message("copy dependencies: changing install names")
+  #--------------------------------------------------------------------------
+  foreach(key ${keys})
+    math(EXPR i ${i}+1)
+    if(APPLE)
+      if(NOT ${${key}_SYSTEMFLAG})
+        message(STATUS "${i}/${n}: changing install names for '${${key}_RESOLVED_EMBEDDED_ITEM}'")
+        change_install_names_for_item(keys "${key}")
+      else(NOT ${${key}_SYSTEMFLAG})
+        message(STATUS "${i}/${n}: skipping install name change for system library: '${${key}_RESOLVED_EMBEDDED_ITEM}'")
+      endif(NOT ${${key}_SYSTEMFLAG})
+    else(APPLE)
+      message(STATUS "${i}/${n}: changing install names not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'")
+    endif(APPLE)
+  endforeach(key)
+
+
+  #--------------------------------------------------------------------------
+  message("copy dependencies: verifying binaries")
+  #--------------------------------------------------------------------------
+  verify(keys)
+
+  #--------------------------------------------------------------------------
+  message("copy dependencies: cleaning up")
+  #--------------------------------------------------------------------------
+  clear_keys(keys)
+
+  #--------------------------------------------------------------------------
+  message("copy dependencies: done")
+  #--------------------------------------------------------------------------
+endfunction(copy_dependencies)
+
+#=============================================================================
+# function collect_dependencies
+#=============================================================================
+function(collect_dependencies binaries keys_var)
+  set(${keys_var} PARENT_SCOPE)
+
+  # First add all binaries to the keys. 
+  # This is done before dependency resolution because one of the binaries could
+  # potentially be a dependency. 
+  foreach(exe ${binaries})
+    get_filename_component(exepath "${exe}" PATH)
+    set_keys_for_item(${keys_var} "${exe}" "${exe}" 0 0)
+  endforeach(exe)
+
+  # Get dependencies and add them to the keys.
+  foreach(exe ${binaries})
+    get_filename_component(exepath "${exe}" PATH)
+    add_dependencies_for_item(${keys_var}  "${exe}" "${exe}" "${exepath}")
+  endforeach(exe)
+
+  # Propagate values to caller's scope:
+  set(${keys_var} ${${keys_var}} PARENT_SCOPE)
+  foreach(key ${${keys_var}})
+    set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE)
+    set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE)
+    set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE)
+    set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE)
+    set(${key}_SYSTEMFLAG "${${key}_SYSTEMFLAG}" PARENT_SCOPE)
+  endforeach(key)
+
+endfunction(collect_dependencies)
+
+
+
+
+#=============================================================================
+# function set_keys_for_item
+#=============================================================================
+function(set_keys_for_item keys_var context resolved_item copyflag systemflag)
+  get_item_key("${resolved_item}" key)
+  list(LENGTH ${keys_var} length_before)
+  append_unique(${keys_var} "${key}")
+  list(LENGTH ${keys_var} length_after)
+
+  if(NOT length_before EQUAL length_after)
+    if(${copyflag} AND (NOT ${systemflag}) )
+      resolve_embedded_item("${resolved_item}" resolved_embedded_item)
+      set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE)
+      if(${resolved_embedded_item} STREQUAL ${resolved_item})
+        set(${key}_COPYFLAG 0 PARENT_SCOPE)
+      else(${resolved_embedded_item} STREQUAL ${resolved_item})
+        set(${key}_COPYFLAG 1 PARENT_SCOPE)
+      endif(${resolved_embedded_item} STREQUAL ${resolved_item})
+      set(${key}_SYSTEMFLAG 0 PARENT_SCOPE)
+    else(${copyflag} AND (NOT ${systemflag}) )
+      set(${key}_COPYFLAG ${copyflag} PARENT_SCOPE)
+      set(${key}_SYSTEMFLAG ${systemflag} PARENT_SCOPE)
+      set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_item}" PARENT_SCOPE)
+    endif(${copyflag} AND (NOT ${systemflag}) )
+    set(${keys_var} ${${keys_var}} PARENT_SCOPE)
+    get_filename_component(item_name "${resolved_item}" NAME)
+    set(${key}_ITEM "${item_name}" PARENT_SCOPE)
+    set(${key}_RESOLVED_ITEM "${resolved_item}" PARENT_SCOPE)
+
+  else(NOT length_before EQUAL length_after)
+    message(FATAL_ERROR "Item key '${key}' already in the list. This should not happen.")
+  endif(NOT length_before EQUAL length_after)
+endfunction(set_keys_for_item)
+
+#=============================================================================
+# function get_item_key
+#=============================================================================
+function(get_item_key item key_var)
+  get_filename_component(item_name "${item}" REALPATH)
+  if(WIN32)
+    string(TOLOWER "${item_name}" item_name)
+  endif()
+  string(REGEX REPLACE "\\." "_" item_name "${item_name}")
+  string(REGEX REPLACE "/" "_"   item_name "${item_name}")
+  string(REGEX REPLACE "\\\\" "_" ${key_var} "${item_name}")
+  set(${key_var} ${${key_var}} PARENT_SCOPE)
+endfunction(get_item_key)
+
+
+#=============================================================================
+# function clear_keys
+#=============================================================================
+function(clear_keys keys_var)
+  foreach(key ${${keys_var}})
+    set(${key}_ITEM PARENT_SCOPE)
+    set(${key}_RESOLVED_ITEM PARENT_SCOPE)
+    set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE)
+    set(${key}_COPYFLAG PARENT_SCOPE)
+    set(${key}_SYSTEMFLAG PARENT_SCOPE)
+  endforeach(key)
+  set(${keys_var} PARENT_SCOPE)
+endfunction(clear_keys)
+
+
+
+#=============================================================================
+# function resolve_item
+#=============================================================================
+function(resolve_item context item exepath resolved_item_var)
+  set(resolved_item "${item}")
+
+  # @rpath references are relative to the paths built into the binaries with -rpath
+  # rpath resolution comes first, as the rpath might contain a @loader_path or @executable_path reference
+  if(resolved_item MATCHES "@rpath")
+    get_rpath("${context}" rpath)
+    string(REPLACE "@rpath/" "${rpath}/" resolved_item "${resolved_item}")
+  endif(resolved_item MATCHES "@rpath")
+
+  # @executable_path references are assumed relative to exepath
+  if(resolved_item MATCHES "@executable_path")
+    string(REPLACE "@executable_path" "${exepath}" resolved_item "${resolved_item}")
+  endif(resolved_item MATCHES "@executable_path")
+
+  # @loader_path references are assumed relative to the
+  # PATH of the given "context" (presumably another library)
+  if(resolved_item MATCHES "@loader_path")
+    get_filename_component(contextpath "${context}" PATH)
+    string(REPLACE "@loader_path" "${contextpath}" resolved_item "${resolved_item}")
+  endif(resolved_item MATCHES "@loader_path")
+  
+  get_filename_component(resolved_item "${resolved_item}" REALPATH)
+
+  if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
+    set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE)
+    return()
+  endif(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
+
+  set(ri "ri-NOTFOUND")
+  find_file(ri "${item}" ${exepath}  NO_DEFAULT_PATH)
+  find_file(ri "${item}" ${exepath}  /usr/lib)
+  if(WIN32 AND NOT UNIX)
+    find_program(ri "${item}" PATHS "${exepath}" NO_DEFAULT_PATH)
+    find_program(ri "${item}" PATHS "${exepath}")
+  endif(WIN32 AND NOT UNIX)
+  if(ri)
+    set(resolved_item "${ri}")
+    set(ri "ri-NOTFOUND")
+  endif(ri)
+
+  if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
+    set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE)
+  else(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
+    message(FATAL_ERROR "cannot resolve item '${item}' in context:'${context}'. Partially resolved path is: '${resolved_item}'")
+  endif(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
+endfunction(resolve_item)
+
+
+#=============================================================================
+# function resolve_embedded_item
+#=============================================================================
+function(resolve_embedded_item item resolved_item_var)
+  if(${item} MATCHES ${CMAKE_INSTALL_PREFIX})
+    # item already embedded
+    set(${resolved_item_var} ${item} PARENT_SCOPE)
+  else(${item} MATCHES ${CMAKE_INSTALL_PREFIX})
+    # only embed libraries, therefore put into lib dir
+    get_filename_component(item_name "${item}" NAME)
+    if ("${CMAKE_NATIVE_ARCH}" MATCHES "64")
+      set(${resolved_item_var} "${CMAKE_INSTALL_PREFIX}/lib64/${item_name}" PARENT_SCOPE)
+    else()
+      set(${resolved_item_var} "${CMAKE_INSTALL_PREFIX}/lib/${item_name}" PARENT_SCOPE)
+    endif()
+  endif(${item} MATCHES ${CMAKE_INSTALL_PREFIX})
+endfunction(resolve_embedded_item)
+
+
+
+#=============================================================================
+# function copy_resolved_item
+#=============================================================================
+function(copy_resolved_item resolved_item resolved_embedded_item)
+  if(WIN32)
+    # ignore case on Windows
+    string(TOLOWER "${resolved_item}" resolved_item_compare)
+    string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
+  else()
+    set(resolved_item_compare "${resolved_item}")
+    set(resolved_embedded_item_compare "${resolved_embedded_item}")
+  endif()
+
+  if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
+    message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...")
+  else()
+    message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}")
+    execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
+    if(UNIX AND NOT APPLE)
+      file(RPATH_REMOVE FILE "${resolved_embedded_item}")
+    endif(UNIX AND NOT APPLE)
+  endif()
+
+endfunction(copy_resolved_item)
+
+
+#=============================================================================
+# function get_rpath
+#=============================================================================
+function(get_rpath target rpath_var)
+  if(APPLE)
+    set(rpath_cmd "otool")
+    set(rpath_cmd_args "-l")
+    set(rpath_cmd_regex ".*LC_RPATH\n *cmdsize [0-9]*\n\ *path ([^\n ]*) \\(offset.*")
+  else(APPLE)
+    MESSAGE( FATAL_ERROR rpath extraction not implemented for non APPLE platform)
+  endif(APPLE)
+  execute_process(
+    COMMAND ${rpath_cmd} ${rpath_cmd_args} ${target}
+    OUTPUT_VARIABLE rpath_cmd_ov
+    RESULT_VARIABLE retcode
+  )
+  if(retcode)
+    MESSAGE(FATAL_ERROR "otool stopped with return code: '${retcode}'")
+  endif(retcode)
+  if(rpath_cmd_ov MATCHES ${rpath_cmd_regex})
+    string(REGEX REPLACE ${rpath_cmd_regex} "\\1"  rpath ${rpath_cmd_ov})
+    set(${rpath_var} ${rpath} PARENT_SCOPE)
+  endif(rpath_cmd_ov MATCHES ${rpath_cmd_regex})
+endfunction(get_rpath)
+
+
+#=============================================================================
+# function change_install_names_for_item
+#=============================================================================
+function(change_install_names_for_item keys_var key)
+
+  # Ensure the item is within the install directory
+  if(${${key}_RESOLVED_EMBEDDED_ITEM} MATCHES ${CMAKE_INSTALL_PREFIX})
+    set(prereqs "")
+    get_filename_component(exepath ${${key}_RESOLVED_EMBEDDED_ITEM} PATH)
+    string(REPLACE ${CMAKE_INSTALL_PREFIX} "" rel_path ${exepath})
+    string(REGEX REPLACE "/[^/]*" "/.." inv_rel_path ${rel_path})
+    get_dependencies_for_item("${${key}_RESOLVED_EMBEDDED_ITEM}" prereqs)
+    set(changes "")
+    foreach(pr ${prereqs})
+      # Each referenced item's key is "rkey" in the loop:
+      get_filename_component(exepath ${${key}_RESOLVED_EMBEDDED_ITEM} PATH)
+      resolve_item( ${${key}_RESOLVED_EMBEDDED_ITEM} ${pr} exepath resolved_pr)
+      get_item_key("${resolved_pr}" rkey)
+      list(FIND ${keys_var} ${rkey} r_found)
+      if(r_found EQUAL -1)
+        MESSAGE(FATAL_ERROR "Key: '${rkey}' not found in dependency list. Cannot change install name.")
+      endif(r_found EQUAL -1)
+      if(NOT ${${rkey}_SYSTEMFLAG})
+        get_filename_component(ref_exepath ${${rkey}_RESOLVED_EMBEDDED_ITEM} PATH)
+        string(REPLACE ${CMAKE_INSTALL_PREFIX} "" ref_rel_path ${ref_exepath})
+        set(diff_path @loader_path${inv_rel_path}${ref_rel_path}/)
+        set(changes ${changes} "-change" "${pr}" "${diff_path}${${rkey}_ITEM}")
+      endif(NOT ${${rkey}_SYSTEMFLAG})
+    endforeach(pr)
+     
+    execute_process(COMMAND chmod u+w "${${key}_RESOLVED_EMBEDDED_ITEM}")
+    
+    # Change this item's id and all of its references in one call
+    # to install_name_tool:
+    execute_process(COMMAND install_name_tool ${changes} -id "${${key}_ITEM}" "${${key}_RESOLVED_EMBEDDED_ITEM}")
+  else( ${${key}_RESOLVED_EMBEDDED_ITEM} MATCHES ${CMAKE_INSTALL_PREFIX})
+    MESSAGE(FATAL_ERROR "Trying to change install name for: ${${key}_RESOLVED_EMBEDDED_ITEM}, which is outside of install directory")
+  endif(${${key}_RESOLVED_EMBEDDED_ITEM} MATCHES ${CMAKE_INSTALL_PREFIX})
+
+endfunction(change_install_names_for_item)
+
+
+
+#=============================================================================
+# function verify
+#=============================================================================
+function(verify keys_var)
+  foreach(key ${${keys_var}})
+    get_dependencies_for_item("${${key}_RESOLVED_EMBEDDED_ITEM}" dependencies)
+    get_filename_component(exepath ${${key}_RESOLVED_EMBEDDED_ITEM} PATH)
+    foreach(dep ${dependencies})
+      resolve_item( ${${key}_RESOLVED_EMBEDDED_ITEM} ${dep} exepath resolved_dep)
+      is_system_lib(${resolved_dep} system_flag)
+      if(NOT ${system_flag})
+        if(NOT ${resolved_dep} MATCHES ${CMAKE_INSTALL_PREFIX})
+          MESSAGE("Warning: item:'${${key}_RESOLVED_EMBEDDED_ITEM}' contains external dependency:'${resolved_dep}'")
+        endif(NOT ${resolved_dep} MATCHES ${CMAKE_INSTALL_PREFIX})
+      endif(NOT ${system_flag})
+    endforeach(dep)
+  endforeach(key)
+endfunction(verify)
+
+
+#=============================================================================
+# macro get_dependencies_for_item
+#=============================================================================
+# Note: dispatcher written as macro to avoid having to copy results to parent scope
+macro(get_dependencies_for_item item list_var)
+
+  if(NOT IS_ABSOLUTE "${item}" AND EXISTS "${item}")
+    message(FATAL_ERROR "Cannot find item: '${item}' to get dependencies.")
+  endif(NOT IS_ABSOLUTE "${item}" AND EXISTS "${item}")
+
+  if(APPLE)
+    get_dependencies_for_item_apple(${item} ${list_var})
+  elseif(WIN32 AND NOT UNIX)
+    get_dependencies_for_item_win(${item} ${list_var})
+  elseif(UNIX)
+    get_dependencies_for_item_linux(${item} ${list_var})
+  else(APPLE)
+    MESSAGE(FATAL_ERROR "Cannot determine dependencies for unknown system.")
+  endif(APPLE)
+endmacro(get_dependencies_for_item)
+
+#=============================================================================
+# function get_dependencies_for_item_apple
+#=============================================================================
+function(get_dependencies_for_item_apple  item list_var)
+
+  find_program(otool_cmd "otool" PATHS "/usr/local/bin" "/usr/bin")
+  if(NOT otool_cmd)
+    message(FATAL_ERROR "Could not find otool - cannot analyze dependencies.")
+    return()
+  endif(NOT otool_cmd)
+
+  execute_process( COMMAND ${otool_cmd} -L ${item} OUTPUT_VARIABLE otool_cmd_ov RESULT_VARIABLE retcode )
+  if(retcode)
+    MESSAGE(FATAL_ERROR "otool stopped with return code: '${retcode}'")
+  endif(retcode)
+  # Convert to a list of lines:
+  string(REGEX REPLACE ";" "\\\\;" candidates "${otool_cmd_ov}")
+  set(eol_char "E")
+  string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}")
+
+  # check for install id and remove it from list, since otool -L can include a reference to itself
+  set(install_id)
+  execute_process( COMMAND ${otool_cmd} -D ${item} OUTPUT_VARIABLE install_id_ov  RESULT_VARIABLE retcode)
+  if(retcode)
+    MESSAGE(FATAL_ERROR "otool stopped with return code: '${retcode}'")
+  endif(retcode)
+  # second line is install name
+  string(REGEX REPLACE ".*:\n" "" install_id "${install_id_ov}")
+  if(install_id)
+    string(REGEX MATCH "[^\n ].*[^\n ]" install_id "${install_id}")
+  endif(install_id)
+
+  # Analyze each line for file names that match the regular expression:
+  set(otool_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$")
+  set(dep_list)
+  foreach(candidate ${candidates})
+    if("${candidate}" MATCHES "${otool_regex}")
+      # Extract information from each candidate:
+      string(REGEX REPLACE "${otool_regex}" "\\1" dep "${candidate}")
+      if(NOT "${dep}" STREQUAL "${install_id}")
+        list(APPEND dep_list ${dep})
+      endif(NOT "${dep}" STREQUAL "${install_id}")
+    endif("${candidate}" MATCHES "${otool_regex}")
+  endforeach(candidate)
+  set(${list_var} ${dep_list} PARENT_SCOPE)
+endfunction(get_dependencies_for_item_apple)
+
+#=============================================================================
+# function get_dependencies_for_item_linux
+#=============================================================================
+function(get_dependencies_for_item_linux  item list_var)
+
+  find_program(ldd_cmd "ldd" PATHS "/usr/local/bin" "/usr/bin")
+  if(NOT ldd_cmd)
+    message(FATAL_ERROR "Could not find ldd - cannot analyze dependencies.")
+    return()
+  endif(NOT ldd_cmd)
+
+  execute_process( COMMAND ${ldd_cmd}  ${item} OUTPUT_VARIABLE ldd_cmd_ov RESULT_VARIABLE retcode)
+  if(retcode)
+    MESSAGE(FATAL_ERROR "ldd stopped with return code: '${retcode}'")
+  endif(retcode)
+  # Convert to a list of lines:
+  string(REGEX REPLACE ";" "\\\\;" candidates "${ldd_cmd_ov}")
+  set(eol_char "E")
+  string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}")
+
+  # Analyze each line for file names that match the regular expression:
+  set(ldd_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$")
+  set(ldd_regex_error "not found${eol_char}$")
+  set(ldd_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$")
+  set(dep_list)
+  foreach(candidate ${candidates})
+    if("${candidate}" MATCHES "${ldd_regex}")
+      # Extract information from each candidate:
+      if("${candidate}" MATCHES "${ldd_regex_error}")
+        string(REGEX REPLACE "${ldd_regex_fallback}" "\\1" raw_item "${candidate}")
+      else("${candidate}" MATCHES "${ldd_regex_error}")
+        string(REGEX REPLACE "${ldd_regex}" "\\1" raw_item "${candidate}")
+      endif("${candidate}" MATCHES "${ldd_regex_error}")
+      list(APPEND depl_list ${dep})
+    endif("${candidate}" MATCHES "${ldd_regex}")
+  set(${list_var} ${dep_list} PARENT_SCOPE)
+endfunction(get_dependencies_for_item_linux)
+
+#=============================================================================
+# function get_dependencies_for_item_win
+#=============================================================================
+function(get_dependencies_for_item_win  item list_var)
+
+  find_program(dumpbin_cmd "dumpbin")
+  if(NOT dumpbin_cmd)
+    message(FATAL_ERROR "Could not find dumpbin - cannot analyze dependencies.")
+    return()
+  endif(NOT dumpbin_cmd)
+
+  set(ENV{VS_UNICODE_OUTPUT} "") # Block extra output from inside VS IDE.
+  execute_process( COMMAND ${dumpbin_cmd}  /dependents ${item} OUTPUT_VARIABLE dumpbin_cmd_ov RESULT_VARIABLE retcode)
+  if(retcode)
+    MESSAGE(FATAL_ERROR "dumpbin stopped with return code: '${retcode}'")
+  endif(retcode)
+  # Convert to a list of lines:
+  string(REGEX REPLACE ";" "\\\\;" candidates "${dumpbin_cmd_ov}")
+  set(eol_char "E")
+  string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}")
+
+  # Analyze each line for file names that match the regular expression:
+  set(dumpbin_regex "^    ([^ ].*[Dd][Ll][Ll])${eol_char}$")
+  set(dep_list)
+  foreach(candidate ${candidates})
+    if("${candidate}" MATCHES "${dumpbin_regex}")
+      # Extract information from each candidate:
+      string(REGEX REPLACE "${dumpbin_regex}" "\\1" raw_item "${candidate}")
+      list(APPEND depl_list ${dep})
+    endif("${candidate}" MATCHES "${dumpbin_regex}")
+  set(${list_var} ${dep_list} PARENT_SCOPE)
+endfunction(get_dependencies_for_item_win)
+
+
+#=============================================================================
+# function add_dependencies_for_item
+#=============================================================================
+function(add_dependencies_for_item keys_var context item exepath)
+  if(NOT IS_ABSOLUTE "${item}" AND EXISTS "${item}")
+    message(FATAL_ERROR "Item '${item}' does not exist.")
+  endif(NOT IS_ABSOLUTE "${item}" AND EXISTS "${item}")
+
+  get_dependencies_for_item(${item} dependencies)
+  foreach(dep ${dependencies})
+    resolve_item("${context}" "${dep}" "${exepath}" resolved_dep)
+    get_item_key("${resolved_dep}" dep_key)
+    list(FIND ${keys_var} ${dep_key} dep_found)
+    if(dep_found EQUAL -1)
+      message(STATUS "Resolved '${dep}' to: '${resolved_dep}'")
+      is_system_lib(${resolved_dep} system_flag)
+      if(${system_flag})
+        MESSAGE(STATUS "Skipping system library:'${resolved_dep}'")
+        set_keys_for_item(${keys_var} ${item} ${resolved_dep} 0 1)
+      else(${system_flag})
+        set_keys_for_item(${keys_var} ${item} ${resolved_dep} 1   0)
+        add_dependencies_for_item(${keys_var} ${item} ${resolved_dep} ${exepath})
+      endif(${system_flag})
+    endif(dep_found EQUAL -1)
+  endforeach(dep ${dependencies})
+  # Propagate values to caller's scope:
+  set(${keys_var} ${${keys_var}} PARENT_SCOPE)
+  foreach(key ${${keys_var}})
+    set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE)
+    set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE)
+    set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE)
+    set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE)
+    set(${key}_SYSTEMFLAG "${${key}_SYSTEMFLAG}" PARENT_SCOPE)
+  endforeach(key)
+endfunction(add_dependencies_for_item)
+
+
+#=============================================================================
+# function is_system_lib
+#=============================================================================
+function(is_system_lib item system_var)
+  if(APPLE)
+    if(item MATCHES "^(/System/Library/|/usr/lib/)")
+      set(${system_var}  1 PARENT_SCOPE)
+    else()
+      set(${system_var}  0 PARENT_SCOPE)
+    endif()
+    return()
+  elseif(WIN32)
+    string(TOLOWER "${item}" lower)
+    string(TOLOWER "$ENV{SystemRoot}" sysroot)
+    string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}")
+    string(TOLOWER "$ENV{windir}" windir)
+    string(REGEX REPLACE "\\\\" "/" windir "${windir}")
+    if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
+      set(${system_var}  1 PARENT_SCOPE)
+    else()
+      set(${system_var}  0 PARENT_SCOPE)
+    endif()
+    return()
+  elseif(UNIX)
+    if(item MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11R6/|/usr/bin/)")
+      set(${system_var} 1 PARENT_SCOPE)
+    else()
+      set(${system_var}  0 PARENT_SCOPE)
+    endif()
+    return()
+  else(APPLE)
+    MESSAGE(FATAL_ERROR "Unknown OS. Cannot determine if item:'${item}' is a system lib.")
+  endif(APPLE)
+endfunction(is_system_lib)
+
+#=============================================================================
+# function append_unique
+#=============================================================================
+function(append_unique list_var value)
+  set(contains 0)
+  foreach(item ${${list_var}})
+    if("${item}" STREQUAL "${value}")
+      set(contains 1)
+      break()
+    endif("${item}" STREQUAL "${value}")
+  endforeach(item)
+
+  if(NOT contains)
+    set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE)
+  endif(NOT contains)
+endfunction(append_unique)
diff --git a/cmake_support/Deployment.cmake b/cmake_support/Deployment.cmake
index e4f6d949b..2c4a6f5ce 100644
--- a/cmake_support/Deployment.cmake
+++ b/cmake_support/Deployment.cmake
@@ -1,41 +1,77 @@
-INCLUDE(InstallRequiredSystemLibraries)
+#-------------------------------------------------------------------------------
+#  Author:  Andreas Schenk
+#
+#  This file contains the configuration for the bundle/installer creation
+#  with CPack.
+#
+#-------------------------------------------------------------------------------
+
 
+INCLUDE(InstallRequiredSystemLibraries)
 
 set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OpenStructure Framework")
 set(CPACK_PACKAGE_VENDOR "2008-2010 -- The OpenStructure Team")
 set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/ReadMe.txt")
 set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.txt")
-set(CPACK_PACKAGE_VERSION_MAJOR "1")
-set(CPACK_PACKAGE_VERSION_MINOR "0")
-if (OST_REV)
-  set(CPACK_PACKAGE_VERSION_PATCH "0a1_${OST_REV}")
-else()
-  set(CPACK_PACKAGE_VERSION_PATCH "0a1")
-endif()
-set(OST_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
-set(CPACK_PACKAGE_INSTALL_DIRECTORY "openstructure ${OST_VERSION}" )
+set(CPACK_PACKAGE_VERSION_MAJOR ${OST_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${OST_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${OST_VERSION_PATCH})
+if(APPLE)
+  set(CPACK_GENERATOR Bundle)
+  set(CPACK_BUNDLE_NAME DNG)
+  set(CPACK_BUNDLE_PLIST ${PROJECT_SOURCE_DIR}/deployment/macos/Info.plist)
+  set (CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/graphics/icon.icns)
+  set (CPACK_BUNDLE_ICON ${PROJECT_SOURCE_DIR}/graphics/icon.icns)
+  install(CODE "if(NOT ${CMAKE_INSTALL_PREFIX} STREQUAL \${CMAKE_INSTALL_PREFIX})
+                  #read ost_config to apply modifications
+                  file(READ \"\${CMAKE_INSTALL_PREFIX}/libexec/openstructure/ost_config\" ost_config)
+                  
+                  set(OST_BINARIES \"\${CMAKE_INSTALL_PREFIX}/bin/lddt\"
+                                   \"\${CMAKE_INSTALL_PREFIX}/bin/chemdict_tool\"
+                                   \"\${CMAKE_INSTALL_PREFIX}/libexec/openstructure/gosty\")
+                  
+                  #copy python into bundle if a version different from the system version is used
+                  if(NOT ${PYTHON_BINARY} MATCHES /usr/bin/)
+                    get_filename_component(real_python_include_path ${PYTHON_INCLUDE_PATH} REALPATH)
+                    get_filename_component(python_root_dir \${real_python_include_path}/../.. REALPATH) 
+                    file(COPY \${python_root_dir}/${LIB_DIR}/python${PYTHON_VERSION}/
+                         DESTINATION \${CMAKE_INSTALL_PREFIX}/${LIB_DIR}/python${PYTHON_VERSION})
+                    file(COPY \${python_root_dir}/Resources/Python.app/Contents/MacOS/Python
+                         DESTINATION \${CMAKE_INSTALL_PREFIX}/bin)
+                    file(COPY ${PYTHON_INCLUDE_PATH}/pyconfig.h
+                         DESTINATION \${CMAKE_INSTALL_PREFIX}/include/python${PYTHON_VERSION})
+
+                    list(APPEND OST_BINARIES \"\${CMAKE_INSTALL_PREFIX}/bin/Python\")
+                    string(REGEX REPLACE \"pyexec=\\\"[^\n\$]*\\\"\"
+                                         \"pyexec=\$DNG_BINDIR/Python\"
+                                         ost_config \"\${ost_config}\")
+                  endif(NOT ${PYTHON_BINARY} MATCHES /usr/bin/)  
+  
+                  file(GLOB_RECURSE OST_PYMODS \"\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}/*.so\")
+                  list(APPEND  OST_BINARIES \${OST_PYMODS})
+                  
+                  #copy Qt stuff
+                  file(COPY \"${QT_LIBRARY_DIR}/Resources/qt_menu.nib\"
+                       DESTINATION \"\${CMAKE_INSTALL_PREFIX}/libexec/openstructure/\"
+                       FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
+                  file(COPY ${QT_PLUGINS_DIR}
+                       DESTINATION \${CMAKE_INSTALL_PREFIX}/libexec/openstructure) 
+                  file(GLOB_RECURSE QT_PLUGINS \"\${CMAKE_INSTALL_PREFIX}/libexec/openstructure/*.dylib\")
+                  list(APPEND  OST_BINARIES \${QT_PLUGINS})
+                  file(WRITE \"\${CMAKE_INSTALL_PREFIX}/libexec/openstructure/qt.conf\" \"[Paths]\nPlugins=../plugins\n\")
+
+                  file(WRITE \"\${CMAKE_INSTALL_PREFIX}/../MacOS/dng\" \"#!/bin/sh\nexec \\\"\\\${0%/*}/../Resources/bin/dng\\\"\n\")
+                  execute_process(COMMAND chmod ugo+x \"\${CMAKE_INSTALL_PREFIX}/../MacOS/dng\")
+                  file(WRITE \"\${CMAKE_INSTALL_PREFIX}/libexec/openstructure/ost_config\" \"\${ost_config}\")
+  
+                  set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake_support)
+                  include(CopyDependencies)
+                  copy_dependencies(\"\${OST_BINARIES}\") 
+                 endif(NOT ${CMAKE_INSTALL_PREFIX} STREQUAL \${CMAKE_INSTALL_PREFIX})")
+  set(CPACK_PACKAGE_INSTALL_DIRECTORY "openstructure ${OST_VERSION_STRING}" )
+endif(APPLE)
 
-IF(WIN32 AND NOT UNIX)
-# There is a bug in NSI that does not handle full unix paths properly. Make
-# sure there is at least one set of four (4) backlashes.
-  set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/graphics/\\\\icon_32.png")
-  #set(CPACK_NSIS_INSTALLED_ICON_NAME "bin/\\\\gosty.exe")
-  set(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY}")
-  set(CPACK_NSIS_HELP_LINK "http:www.openstructure.org")
-  #set(CPACK_NSIS_URL_INFO_ABOUT "http:www.my-personal-home-page.com")
-  set(CPACK_NSIS_CONTACT "users@maillist.unibas.ch")
-  set(CPACK_NSIS_MODIFY_PATH ON)
-  set(CPACK_SOURCE_STRIP_FILES TRUE)
-  set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/graphics/\\\\icon_32.png")
-  set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "")
-  set(CPACK_PACKAGE_EXECUTABLES "dng" "DNG")
-  set(CPACK_PACKAGE_EXECUTABLES "gipltng" "gipltng")
-  set(CPACK_INSTALL_DIRECTORIES "d:/progams/lib")
-  # set(CPACK_STRIP_FILES "bin/gosty" 
-                        # "bin/ost.bat"
-                        # "bin/ost_cl.bat"
-                        # "bin\gipltng.bat
-  # )
-ENDIF()
 
-INCLUDE(CPack) #this must come last
+if(${PROJECT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
+  INCLUDE(CPack) #only include CPack for the main project and not for subprojects
+endif(${PROJECT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
diff --git a/deployment/macos/Info.plist b/deployment/macos/Info.plist
new file mode 100644
index 000000000..5169acff8
--- /dev/null
+++ b/deployment/macos/Info.plist
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 
+    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>English</string>
+  <key>CFBundleExecutable</key>
+  <string>dng</string>
+  <key>CFBundleName</key>
+  <string>DNG</string>  
+  <key>CFBundleDisplayName</key>
+  <string>DNG</string>  
+  <key>CFBundleHelpBookFolder</key>
+  <string>OpenStructure Manual</string>
+  <key>CFBundleHelpBookName</key>
+  <string>OpenStructure Help</string>
+  <key>CFBundleIconFile</key>
+  <string>DNG.icns</string>
+  <key>CFBundleIdentifier</key>
+  <string>org.openstructure.OpenStructure</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundlePackageType</key>
+  <string>APPL</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string></string>
+  <key>NSMainNibFile</key>
+  <string>MainMenu</string>
+  <key>NSPrincipalClass</key>
+  <string>NSApplication</string>
+</dict>
+</plist>
-- 
GitLab