Created
September 3, 2018 23:40
-
-
Save multiplemonomials/35b4722b1caae14be39c5f1e291a8aee to your computer and use it in GitHub Desktop.
BundleOSXDependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script run at install-time to: | |
# * locate dependencies of executables and libraries | |
# * copy them to the install directory | |
# * fix the install_name of the depender to point to the dependency on the RPATH | |
# * add a new RPATH entry on the dependency for its new location and remove its old install name | |
# * repeat above for dependencies of the dependency, and all other dependencies | |
# arguments: | |
# PACKAGE_PREFIX -- root of a UNIX-structure package to operate on | |
# CMAKE_SHARED_LIBRARY_SUFFIX -- pass this variable in from your CMake script | |
# CMAKE_EXECUTABLE_SUFFIX -- pass this variable in from your CMake script | |
# EXTRA_RPATH_SEARCH_DIRS -- list of extra directories to search in when trying to resolve @rpath references | |
# PREFIX_RELATIVE_PYTHONPATH -- pass this variable in from your CMake script to indicate | |
# where relative to PACKAGE_PREFIX your python packages are located. Empty string means | |
# no python packages | |
# notes: | |
# * assumes that ${PACKAGE_PREFIX}/lib is, and should be, the rpath for all internal and external libraries | |
# * does not handle @executable_path since Amber doesn't use it; only handles @rpath and @loader_path | |
# This script was inspired by Hai Nguyen's similar script at https://github.com/Amber-MD/ambertools-binary-build/blob/master/conda_tools/update_gfortran_libs_osx.py | |
include(GetPrerequisites) | |
include(${CMAKE_CURRENT_LIST_DIR}/../Shorthand.cmake) | |
# Returns true iff the given dependency library should be ignored and not copied to the prefix | |
function(should_ignore_dep_library LIB_PATH OUTPUT_VARIABLE) | |
if("${LIB_PATH}" MATCHES "\\.framework") | |
set(${OUTPUT_VARIABLE} 1 PARENT_SCOPE) | |
elseif("${LIB_PATH}" MATCHES "libSystem") | |
set(${OUTPUT_VARIABLE} 1 PARENT_SCOPE) | |
else() | |
set(${OUTPUT_VARIABLE} 0 PARENT_SCOPE) | |
endif() | |
endfunction(should_ignore_dep_library) | |
# Makes sure that the library named by LIB_PATH has the given RPATH location | |
function(add_rpath LIB_PATH RPATH) | |
message(STATUS ">>>> Adding RPATH of \"${RPATH}\" to ${LIB_PATH}") | |
execute_process(COMMAND install_name_tool | |
-add_rpath ${RPATH} ${LIB_PATH} | |
ERROR_VARIABLE INT_ERROR_OUTPUT | |
RESULT_VARIABLE INT_RESULT_CODE) | |
# uhhh, I really hope the user has their language set to English... | |
if("${INT_ERROR_OUTPUT}" MATCHES "would duplicate path") | |
# do nothing, it already exists which is OK | |
elseif(NOT ${INT_RESULT_CODE} EQUAL 0) | |
message("!! Failed to execute install_name_tool! Error message was: ${INT_ERROR_OUTPUT}") | |
endif() | |
endfunction(add_rpath) | |
# Changes the install_name that the given library refers to a dependency by. | |
# Does nothing if OLD_INSTNAME is not valid. | |
function(change_dependency_instname LIB_PATH OLD_INSTNAME NEW_INSTNAME) | |
message(STATUS ">>>> Changing dependency reference \"${OLD_INSTNAME}\" in ${LIB_PATH} to \"${NEW_INSTNAME}\"") | |
execute_process(COMMAND install_name_tool | |
-change ${OLD_INSTNAME} ${NEW_INSTNAME} ${LIB_PATH} | |
ERROR_VARIABLE INT_ERROR_OUTPUT | |
RESULT_VARIABLE INT_RESULT_CODE) | |
# uhhh, I really hope the user has their language set to English... | |
if(NOT ${INT_RESULT_CODE} EQUAL 0) | |
message("!! Failed to execute install_name_tool! Error message was: ${INT_ERROR_OUTPUT}") | |
endif() | |
endfunction(change_dependency_instname) | |
# Sets the install name (the name that other libraries save at link time, and use at runtime to find the library) of the given library to INSTALL_NAME | |
function(set_install_name LIB_PATH INSTALL_NAME) | |
message(STATUS ">> Setting install name of ${LIB_PATH} to \"${INSTALL_NAME}\"") | |
execute_process(COMMAND install_name_tool | |
-id ${INSTALL_NAME} ${LIB_PATH} | |
ERROR_VARIABLE INT_ERROR_OUTPUT | |
RESULT_VARIABLE INT_RESULT_CODE) | |
if(NOT ${INT_RESULT_CODE} EQUAL 0) | |
message(STATUS "!! Failed to execute install_name_tool! Error message was: ${INT_ERROR_OUTPUT}") | |
endif() | |
endfunction(set_install_name) | |
# uses the file command to determine if a file is an executable or a shared library | |
function(is_executable_or_library OUTPUT_VARIABLE FILE_PATH) | |
execute_process(COMMAND file ${FILE_PATH} | |
ERROR_VARIABLE FILE_CMD_ERROR_OUTPUT | |
OUTPUT_VARIABLE FILE_CMD_OUTPUT | |
RESULT_VARIABLE FILE_CMD_RESULT_CODE) | |
if(NOT ${FILE_CMD_RESULT_CODE} EQUAL 0) | |
message(STATUS "!! Failed to execute file! Error message was: ${FILE_CMD_ERROR_OUTPUT}") | |
set(${OUTPUT_VARIABLE} FALSE PARENT_SCOPE) | |
return() | |
endif() | |
if("${FILE_PATH}" MATCHES "\\.dSYM") | |
# ignore debugging symbol libraries | |
set(${OUTPUT_VARIABLE} FALSE PARENT_SCOPE) | |
elseif("${FILE_CMD_OUTPUT}" MATCHES "Mach-O universal binary" OR "${FILE_CMD_OUTPUT}" MATCHES "Mach-O .+ executable") | |
#executables | |
set(${OUTPUT_VARIABLE} TRUE PARENT_SCOPE) | |
elseif("${FILE_CMD_OUTPUT}" MATCHES "Mach-O .+ dynamically linked shared library" OR "${FILE_CMD_OUTPUT}" MATCHES "Mach-O .+ bundle") | |
#shared libraries | |
set(${OUTPUT_VARIABLE} TRUE PARENT_SCOPE) | |
else() | |
#everything else | |
set(${OUTPUT_VARIABLE} FALSE PARENT_SCOPE) | |
endif() | |
endfunction(is_executable_or_library) | |
message("Bundling OSX dependencies for package rooted at: ${PACKAGE_PREFIX}") | |
file(GLOB PACKAGE_LIBRARIES "${PACKAGE_PREFIX}/lib/*${CMAKE_SHARED_LIBRARY_SUFFIX}") | |
file(GLOB PACKAGE_EXECUTABLES "${PACKAGE_PREFIX}/bin/*") | |
if(NOT "${PREFIX_RELATIVE_PYTHONPATH}" STREQUAL "") | |
# note: on OS X, python extension modules use ".so", not ".dylib" | |
file(GLOB_RECURSE PYTHON_EXTENSION_MODULES "${PACKAGE_PREFIX}${PREFIX_RELATIVE_PYTHONPATH}/*.so") | |
else() | |
set(PYTHON_EXTENSION_MODULES "") | |
endif() | |
# items are taken from, and added to, this stack. | |
# All files in this list are already in the installation prefix, and already have correct RPATHs | |
set(ITEMS_TO_PROCESS ${PACKAGE_LIBRARIES} ${PACKAGE_EXECUTABLES} ${PYTHON_EXTENSION_MODULES}) | |
# lists of completed items (can skip if we see a dependency on these) | |
# This always contains the path inside the prefix | |
set(PROCESSED_ITEMS_BY_NEW_PATH "") | |
# List of external libraries which have already been copied to the prefix (by their external paths) | |
set(COPIED_EXTERNAL_DEPENDENCIES "") | |
# List that matches each index in the above list with the new path of the library | |
set(COPIED_EXTERNAL_DEPS_NEW_PATHS "") | |
if(NOT DEFINED EXTRA_RPATH_SEARCH_DIRS) | |
set(EXTRA_RPATH_SEARCH_DIRS "") | |
endif() | |
# always use the prefix lib folder as the first RPATH search dir | |
set(RPATH_SEARCH_DIRS "${PACKAGE_PREFIX}/lib" ${EXTRA_RPATH_SEARCH_DIRS}) | |
while(1) | |
list(LENGTH ITEMS_TO_PROCESS NUM_ITEMS_LEFT) | |
if(${NUM_ITEMS_LEFT} EQUAL 0) | |
break() | |
endif() | |
list(GET ITEMS_TO_PROCESS 0 CURRENT_ITEM) | |
message(STATUS "Considering ${CURRENT_ITEM}") | |
is_executable_or_library(IS_EXEC_OR_LIB "${CURRENT_ITEM}") | |
if(IS_EXEC_OR_LIB) | |
set(CURRENT_ITEM_PREREQUISITES "") | |
get_prerequisites(${CURRENT_ITEM} CURRENT_ITEM_PREREQUISITES 0 0 "" ${PACKAGE_PREFIX}/lib ${PACKAGE_PREFIX}/lib) | |
foreach(PREREQUISITE_LIB_REFERENCE ${CURRENT_ITEM_PREREQUISITES}) | |
should_ignore_dep_library(${PREREQUISITE_LIB_REFERENCE} SHOULD_IGNORE_PREREQUISITE) | |
if(SHOULD_IGNORE_PREREQUISITE) | |
message(STATUS ">> Ignoring dependency: ${PREREQUISITE_LIB_REFERENCE}") | |
else() | |
# resolve RPATH references | |
if("${PREREQUISITE_LIB_REFERENCE}" MATCHES "^@rpath") | |
string(REPLACE "@rpath" "" RPATH_SUFFIX_PATH "${PREREQUISITE_LIB_REFERENCE}") | |
# find the first folder in our RPATH search dirs that contains the library | |
set(PREREQUISITE_LIB "") | |
foreach(SEARCH_DIR ${RPATH_SEARCH_DIRS}) | |
#message("Checking ${SEARCH_DIR}${RPATH_SUFFIX_PATH}") | |
if(EXISTS "${SEARCH_DIR}${RPATH_SUFFIX_PATH}") | |
set(PREREQUISITE_LIB ${SEARCH_DIR}${RPATH_SUFFIX_PATH}) | |
endif() | |
endforeach() | |
else() | |
set(PREREQUISITE_LIB ${PREREQUISITE_LIB_REFERENCE}) | |
endif() | |
if(NOT EXISTS "${PREREQUISITE_LIB}") | |
message("!! Unable to resolve library dependency ${PREREQUISITE_LIB_REFERENCE} -- skipping") | |
else() | |
# check if we already know about this library, and copy it here if we don't | |
list(FIND COPIED_EXTERNAL_DEPENDENCIES "${PREREQUISITE_LIB}" INDEX_IN_COPIED_DEPS) | |
list(FIND PACKAGE_LIBRARIES "${PREREQUISITE_LIB}" INDEX_IN_PACKAGE_LIBRARIES) | |
if(NOT INDEX_IN_COPIED_DEPS EQUAL -1) | |
message(STATUS ">> Already copied dependency: ${PREREQUISITE_LIB}") | |
list(GET COPIED_EXTERNAL_DEPS_NEW_PATHS ${INDEX_IN_COPIED_DEPS} PREREQ_LIB_REALPATH) | |
elseif(NOT INDEX_IN_PACKAGE_LIBRARIES EQUAL -1) | |
message(STATUS ">> Dependency is internal: ${PREREQUISITE_LIB}") | |
set(PREREQ_LIB_REALPATH ${PREREQUISITE_LIB}) | |
else() | |
# previously unseen library -- copy to the prefix and queue for processing | |
message(STATUS ">> Copy library dependency: ${PREREQUISITE_LIB}") | |
# resolve symlinks | |
get_filename_component(PREREQ_LIB_REALPATH ${PREREQUISITE_LIB} REALPATH) | |
file(COPY "${PREREQ_LIB_REALPATH}" DESTINATION ${PACKAGE_PREFIX}/lib FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ) | |
# find new filename | |
get_filename_component(PREREQ_LIB_FILENAME "${PREREQ_LIB_REALPATH}" NAME) | |
set(NEW_PREREQ_PATH "${PACKAGE_PREFIX}/lib/${PREREQ_LIB_FILENAME}") | |
# add correct RPATH | |
add_rpath(${NEW_PREREQ_PATH} "@loader_path/../lib") | |
list(APPEND COPIED_EXTERNAL_DEPENDENCIES ${PREREQUISITE_LIB}) | |
list(APPEND COPIED_EXTERNAL_DEPS_NEW_PATHS ${NEW_PREREQ_PATH}) | |
list(APPEND ITEMS_TO_PROCESS ${NEW_PREREQ_PATH}) | |
endif() | |
# now, update how CURRENT_ITEM refers to this prerequisite | |
get_filename_component(PREREQUISITE_FILENAME "${PREREQ_LIB_REALPATH}" NAME) | |
if(NOT "${PREREQUISITE_LIB_REFERENCE}" STREQUAL "@rpath/${PREREQUISITE_FILENAME}") | |
change_dependency_instname(${CURRENT_ITEM} ${PREREQUISITE_LIB_REFERENCE} "@rpath/${PREREQUISITE_FILENAME}") | |
endif() | |
endif() | |
endif() | |
endforeach() | |
if("${CURRENT_ITEM}" MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") | |
# if it's a library, set its install name to refer to it on the RPATH (so anything can link to it as long as it uses the $AMBERHOME/lib RPATH) | |
get_filename_component(CURRENT_ITEM_FILENAME "${CURRENT_ITEM}" NAME) | |
set_install_name(${CURRENT_ITEM} "@rpath/${CURRENT_ITEM_FILENAME}") | |
endif() | |
list(APPEND PROCESSED_ITEMS_BY_NEW_PATH ${CURRENT_ITEM}) | |
else(IS_EXEC_OR_LIB) | |
message(STATUS ">> Not an executable or shared library, skipping: ${CURRENT_ITEM}") | |
endif() | |
list(REMOVE_AT ITEMS_TO_PROCESS 0) | |
endwhile() | |
message(STATUS "Dependency bundling done!") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Dummy subdirectory to run BundleOSXDependencies at install time. | |
# Place this file and BundleOSXDependencies.cmake into a subfolder, then add this subfolder as | |
# a subdirectory at the end of your top-level CMakeLists.txt (insuring it gets installed last). | |
# This file does nothing if CMAKE_SYSTEM_NAME is not Darwin | |
if(${CMAKE_SYSTEM_NAME} STREQUAL Darwin) | |
# Add extra RPATH directories here | |
set(EXTRA_RPATH_SEARCH_DIRS "") | |
# create an install rule to invoke BundleOSXDependencies.cmake | |
install(CODE " | |
message(STATUS \"Finding and bundling dependencies...\") | |
execute_process(COMMAND ${CMAKE_COMMAND} | |
\"-DPACKAGE_PREFIX=\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}\" | |
-DCMAKE_SHARED_LIBRARY_SUFFIX=${CMAKE_SHARED_LIBRARY_SUFFIX} | |
-DCMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} | |
\"-DEXTRA_RPATH_SEARCH_DIRS=${EXTRA_RPATH_SEARCH_DIRS}\" | |
-P ${CMAKE_CURRENT_SOURCE_DIR}/BundleOSXDependencies.cmake)") | |
endif() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment