This is done by gathering LC_RPATH commands for main bundle executable and using it for @rpath lookup in dependent frameworks.
To achieve this all utility functions now take path to executable rather than path to its directory. This enabled apps using @rpath to be bundled correctly, which will be necessary for upcoming Qt 5.4 that will use @rpath for all frameworks. --- Modules/BundleUtilities.cmake | 174 +++++++++++++++++++++++++---------------- Modules/GetPrerequisites.cmake | 55 +++++++------ 2 files changed, 137 insertions(+), 92 deletions(-) diff --git a/Modules/BundleUtilities.cmake b/Modules/BundleUtilities.cmake index 7e2b173..ab26157 100644 --- a/Modules/BundleUtilities.cmake +++ b/Modules/BundleUtilities.cmake @@ -19,6 +19,7 @@ # get_bundle_and_executable # get_bundle_all_executables # get_item_key +# get_item_rpaths # clear_bundle_keys # set_bundle_key_values # get_bundle_keys @@ -75,7 +76,7 @@ # # :: # -# GET_DOTAPP_DIR(<exe> <dotapp_dir_var>) +# GET_DOTAPP_DIR(<executable> <dotapp_dir_var>) # # Returns the nearest parent dir whose name ends with ".app" given the # full path to an executable. If there is no such parent dir, then @@ -123,7 +124,7 @@ # # :: # -# SET_BUNDLE_KEY_VALUES(<keys_var> <context> <item> <exepath> <dirs> +# SET_BUNDLE_KEY_VALUES(<keys_var> <context> <item> <executable> <dirs> # <copyflag>) # # Add a key to the list (if necessary) for the given item. If added, @@ -163,7 +164,7 @@ # # :: # -# FIXUP_BUNDLE_ITEM(<resolved_embedded_item> <exepath> <dirs>) +# FIXUP_BUNDLE_ITEM(<resolved_embedded_item> <executable> <dirs>) # # Get the direct/non-system prerequisites of the resolved embedded item. # For each prerequisite, change the way it is referenced to the value of @@ -189,11 +190,11 @@ # # :: # -# VERIFY_BUNDLE_PREREQUISITES(<bundle> <result_var> <info_var>) +# VERIFY_BUNDLE_PREREQUISITES(<bundle> <result_var> <info_var> [<executable>]) # # Verifies that the sum of all prerequisites of all files inside the -# bundle are contained within the bundle or are "system" libraries, -# presumed to exist everywhere. +# bundle with given optional main executable location are contained within the +# bundle or are "system" libraries, presumed to exist everywhere. # # :: # @@ -285,8 +286,8 @@ function(get_bundle_main_executable bundle result_var) endfunction() -function(get_dotapp_dir exe dotapp_dir_var) - set(s "${exe}") +function(get_dotapp_dir executable dotapp_dir_var) + set(s "${executable}") if(s MATCHES "/.*\\.app/") # If there is a ".app" parent directory, @@ -407,6 +408,29 @@ function(get_bundle_all_executables bundle exes_var) endfunction() +function(get_item_rpaths item rpaths_var) + if(APPLE) + find_program(otool_cmd "otool") + mark_as_advanced(otool_cmd) + endif() + + if(otool_cmd) + execute_process( + COMMAND "${otool_cmd}" -l "${item}" + OUTPUT_VARIABLE load_cmds_ov + ) + string(REGEX REPLACE "[^\n]+cmd LC_RPATH\n[^\n]+\n[^\n]+path ([^\n]+) \\(offset[^\n]+\n" "rpath \\1\n" load_cmds_ov "${load_cmds_ov}") + string(REGEX MATCHALL "rpath [^\n]+" load_cmds_ov "${load_cmds_ov}") + string(REGEX REPLACE "rpath " "" load_cmds_ov "${load_cmds_ov}") + if(load_cmds_ov) + gp_append_unique(${rpaths_var} "${load_cmds_ov}") + endif() + endif() + + set(${rpaths_var} ${${rpaths_var}} PARENT_SCOPE) +endfunction() + + function(get_item_key item key_var) get_filename_component(item_name "${item}" NAME) if(WIN32) @@ -425,12 +449,13 @@ function(clear_bundle_keys keys_var) set(${key}_EMBEDDED_ITEM PARENT_SCOPE) set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE) set(${key}_COPYFLAG PARENT_SCOPE) + set(${key}_RPATHS PARENT_SCOPE) endforeach() set(${keys_var} PARENT_SCOPE) endfunction() -function(set_bundle_key_values keys_var context item exepath dirs copyflag) +function(set_bundle_key_values keys_var context item executable dirs copyflag) get_filename_component(item_name "${item}" NAME) get_item_key("${item}" key) @@ -440,10 +465,19 @@ function(set_bundle_key_values keys_var context item exepath dirs copyflag) list(LENGTH ${keys_var} length_after) if(NOT length_before EQUAL length_after) - gp_resolve_item("${context}" "${item}" "${exepath}" "${dirs}" resolved_item) + # Always use the exepath of the main bundle executable for @executable_path + # replacements: + # + get_filename_component(exepath "${executable}" PATH) + + get_item_key("${executable}" executable_key) + + gp_resolve_item("${context}" "${item}" "${executable}" "${dirs}" resolved_item "${${executable_key}_RPATHS}") gp_item_default_embedded_path("${item}" default_embedded_path) + get_item_rpaths("${resolved_item}" rpaths) + if(item MATCHES "[^/]+\\.framework/") # For frameworks, construct the name under the embedded path from the # opening "${item_name}.framework/" to the closing "/${item_name}": @@ -479,6 +513,7 @@ function(set_bundle_key_values keys_var context item exepath dirs copyflag) set(${key}_EMBEDDED_ITEM "${embedded_item}" PARENT_SCOPE) set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE) set(${key}_COPYFLAG "${copyflag}" PARENT_SCOPE) + set(${key}_RPATHS "${rpaths}" PARENT_SCOPE) else() #message("warning: item key '${key}' already in the list, subsequent references assumed identical to first") endif() @@ -490,14 +525,9 @@ function(get_bundle_keys app libs dirs keys_var) get_bundle_and_executable("${app}" bundle executable valid) if(valid) - # Always use the exepath of the main bundle executable for @executable_path - # replacements: - # - get_filename_component(exepath "${executable}" PATH) - # But do fixups on all executables in the bundle: # - get_bundle_all_executables("${bundle}" exes) + get_bundle_all_executables("${bundle}" file_list) # For each extra lib, accumulate a key as well and then also accumulate # any of its prerequisites. (Extra libs are typically dynamically loaded @@ -505,12 +535,12 @@ function(get_bundle_keys app libs dirs keys_var) # but that do not show up in otool -L output...) # foreach(lib ${libs}) - set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${exepath}" "${dirs}" 0) + set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${executable}" "${dirs}" 0) set(prereqs "") - get_prerequisites("${lib}" prereqs 1 1 "${exepath}" "${dirs}") + get_prerequisites("${lib}" prereqs 1 1 "${executable}" "${dirs}") foreach(pr ${prereqs}) - set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${exepath}" "${dirs}" 1) + set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${executable}" "${dirs}" 1) endforeach() endforeach() @@ -518,17 +548,19 @@ function(get_bundle_keys app libs dirs keys_var) # The list of keys should be complete when all prerequisites of all # binaries in the bundle have been analyzed. # - foreach(exe ${exes}) + foreach(f ${file_list}) # Add the exe itself to the keys: # - set_bundle_key_values(${keys_var} "${exe}" "${exe}" "${exepath}" "${dirs}" 0) + set_bundle_key_values(${keys_var} "${f}" "${f}" "${executable}" "${dirs}" 0) + + get_item_key("${executable}" executable_key) # Add each prerequisite to the keys: # set(prereqs "") - get_prerequisites("${exe}" prereqs 1 1 "${exepath}" "${dirs}") + get_prerequisites("${f}" prereqs 1 1 "${executable}" "${dirs}" "${${executable_key}_RPATHS}") foreach(pr ${prereqs}) - set_bundle_key_values(${keys_var} "${exe}" "${pr}" "${exepath}" "${dirs}" 1) + set_bundle_key_values(${keys_var} "${f}" "${pr}" "${executable}" "${dirs}" 1) endforeach() endforeach() @@ -542,6 +574,7 @@ function(get_bundle_keys app libs dirs keys_var) set(${key}_EMBEDDED_ITEM "${${key}_EMBEDDED_ITEM}" PARENT_SCOPE) set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE) set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE) + set(${key}_RPATHS "${${key}_RPATHS}" PARENT_SCOPE) endforeach() endif() endfunction() @@ -612,7 +645,7 @@ function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_ite endfunction() -function(fixup_bundle_item resolved_embedded_item exepath dirs) +function(fixup_bundle_item resolved_embedded_item executable dirs) # This item's key is "ikey": # get_item_key("${resolved_embedded_item}" ikey) @@ -622,7 +655,7 @@ function(fixup_bundle_item resolved_embedded_item exepath dirs) # tree, or in other varied locations around the file system, with our call to # install_name_tool. Make sure that doesn't happen here: # - get_dotapp_dir("${exepath}" exe_dotapp_dir) + get_dotapp_dir("${executable}" exe_dotapp_dir) string(LENGTH "${exe_dotapp_dir}/" exe_dotapp_dir_length) string(LENGTH "${resolved_embedded_item}" resolved_embedded_item_length) set(path_too_short 0) @@ -647,8 +680,10 @@ function(fixup_bundle_item resolved_embedded_item exepath dirs) message(FATAL_ERROR "cannot fixup an item that is not in the bundle...") endif() + get_item_key("${executable}" executable_key) + set(prereqs "") - get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${exepath}" "${dirs}") + get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${executable}" "${dirs}" "${${executable_key}_RPATHS}") set(changes "") @@ -668,12 +703,20 @@ function(fixup_bundle_item resolved_embedded_item exepath dirs) execute_process(COMMAND chmod u+w "${resolved_embedded_item}") endif() + foreach(rpath ${${ikey}_RPATHS}) + set(changes ${changes} -delete_rpath "${rpath}") + endforeach() + + if(${ikey}_EMBEDDED_ITEM) + set(changes ${changes} -id "${${ikey}_EMBEDDED_ITEM}") + endif() + # 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 "${${ikey}_EMBEDDED_ITEM}" "${resolved_embedded_item}" - ) + if(changes) + execute_process(COMMAND install_name_tool ${changes} "${resolved_embedded_item}") + endif() endfunction() @@ -685,8 +728,6 @@ function(fixup_bundle app libs dirs) get_bundle_and_executable("${app}" bundle executable valid) if(valid) - get_filename_component(exepath "${executable}" PATH) - message(STATUS "fixup_bundle: preparing...") get_bundle_keys("${app}" "${libs}" "${dirs}" keys) @@ -732,7 +773,7 @@ function(fixup_bundle app libs dirs) math(EXPR i ${i}+1) if(APPLE) message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'") - fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}") + fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${executable}" "${dirs}") else() message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'") endif() @@ -761,51 +802,46 @@ function(verify_bundle_prerequisites bundle result_var info_var) set(result 1) set(info "") set(count 0) + set(executable "${ARGV3}") - get_bundle_main_executable("${bundle}" main_bundle_exe) - - file(GLOB_RECURSE file_list "${bundle}/*") + get_bundle_all_executables("${bundle}" file_list) foreach(f ${file_list}) - is_file_executable("${f}" is_executable) - if(is_executable) - get_filename_component(exepath "${f}" PATH) - math(EXPR count "${count} + 1") + math(EXPR count "${count} + 1") - message(STATUS "executable file ${count}: ${f}") + message(STATUS "executable file ${count}: ${f}") - set(prereqs "") - get_prerequisites("${f}" prereqs 1 1 "${exepath}" "") + set(prereqs "") + get_prerequisites("${f}" prereqs 1 1 "${executable}" "") - # On the Mac, - # "embedded" and "system" prerequisites are fine... anything else means - # the bundle's prerequisites are not verified (i.e., the bundle is not - # really "standalone") - # - # On Windows (and others? Linux/Unix/...?) - # "local" and "system" prereqs are fine... - # - set(external_prereqs "") + # On the Mac, + # "embedded" and "system" prerequisites are fine... anything else means + # the bundle's prerequisites are not verified (i.e., the bundle is not + # really "standalone") + # + # On Windows (and others? Linux/Unix/...?) + # "local" and "system" prereqs are fine... + # + set(external_prereqs "") - foreach(p ${prereqs}) - set(p_type "") - gp_file_type("${f}" "${p}" p_type) + foreach(p ${prereqs}) + set(p_type "") + gp_file_type("${f}" "${p}" p_type "${executable}") - if(APPLE) - if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system") - set(external_prereqs ${external_prereqs} "${p}") - endif() - else() - if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system") - set(external_prereqs ${external_prereqs} "${p}") - endif() + if(APPLE) + if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system") + set(external_prereqs ${external_prereqs} "${p}") + endif() + else() + if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system") + set(external_prereqs ${external_prereqs} "${p}") endif() - endforeach() - - if(external_prereqs) - # Found non-system/somehow-unacceptable prerequisites: - set(result 0) - set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n") endif() + endforeach() + + if(external_prereqs) + # Found non-system/somehow-unacceptable prerequisites: + set(result 0) + set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n") endif() endforeach() @@ -845,7 +881,7 @@ function(verify_app app) # Verify that the bundle does not have any "external" prerequisites: # - verify_bundle_prerequisites("${bundle}" verified info) + verify_bundle_prerequisites("${bundle}" verified info "${executable}") message(STATUS "verified='${verified}'") message(STATUS "info='${info}'") message(STATUS "") diff --git a/Modules/GetPrerequisites.cmake b/Modules/GetPrerequisites.cmake index 49443e3..0684f12 100644 --- a/Modules/GetPrerequisites.cmake +++ b/Modules/GetPrerequisites.cmake @@ -41,7 +41,7 @@ # :: # # GET_PREREQUISITES(<target> <prerequisites_var> <exclude_system> <recurse> -# <exepath> <dirs>) +# <executable> <dirs> [<rpaths>]) # # Get the list of shared library files required by <target>. The list # in the variable named <prerequisites_var> should be empty on first @@ -53,7 +53,7 @@ # <exclude_system> must be 0 or 1 indicating whether to include or # exclude "system" prerequisites. If <recurse> is set to 1 all # prerequisites will be found recursively, if set to 0 only direct -# prerequisites are listed. <exepath> is the path to the top level +# prerequisites are listed. <executable> is the path to the top level # executable used for @executable_path replacment on the Mac. <dirs> is # a list of paths where libraries might be found: these paths are # searched first when a target without any path info is given. Then @@ -113,7 +113,8 @@ # # :: # -# GP_RESOLVE_ITEM(<context> <item> <exepath> <dirs> <resolved_item_var>) +# GP_RESOLVE_ITEM(<context> <item> <executable> <dirs> <resolved_item_var> +# [<rpaths>]) # # Resolve an item into an existing full path file. # @@ -122,13 +123,14 @@ # # :: # -# GP_RESOLVED_FILE_TYPE(<original_file> <file> <exepath> <dirs> <type_var>) +# GP_RESOLVED_FILE_TYPE(<original_file> <file> <executable> <dirs> <type_var> +# [<rpaths>]) # # Return the type of <file> with respect to <original_file>. String # describing type of prerequisite is returned in variable named # <type_var>. # -# Use <exepath> and <dirs> if necessary to resolve non-absolute <file> +# Use <executable> and <dirs> if necessary to resolve non-absolute <file> # values -- but only for non-embedded items. # # Possible types are: @@ -145,11 +147,12 @@ # # :: # -# GP_FILE_TYPE(<original_file> <file> <type_var>) +# GP_FILE_TYPE(<original_file> <file> <type_var> [<executable>]) # # Return the type of <file> with respect to <original_file>. String # describing type of prerequisite is returned in variable named -# <type_var>. +# <type_var>. Optional executable is used to resolve executable relative +# library locations. # # Possible types are: # @@ -318,9 +321,12 @@ function(gp_item_default_embedded_path item default_embedded_path_var) endfunction() -function(gp_resolve_item context item exepath dirs resolved_item_var) +function(gp_resolve_item context item executable dirs resolved_item_var) set(resolved 0) set(resolved_item "${item}") + set(rpaths "${ARGV5}") + + get_filename_component(exepath "${executable}" PATH) # Is it already resolved? # @@ -331,7 +337,7 @@ function(gp_resolve_item context item exepath dirs resolved_item_var) if(NOT resolved) if(item MATCHES "^@executable_path") # - # @executable_path references are assumed relative to exepath + # @executable_path references are assumed relative to executable # string(REPLACE "@executable_path" "${exepath}" ri "${item}") get_filename_component(ri "${ri}" ABSOLUTE) @@ -375,9 +381,9 @@ function(gp_resolve_item context item exepath dirs resolved_item_var) string(REPLACE "@rpath/" "" norpath_item "${item}") set(ri "ri-NOTFOUND") - find_file(ri "${norpath_item}" ${exepath} ${dirs} NO_DEFAULT_PATH) + find_file(ri "${norpath_item}" ${dirs} ${rpaths} NO_DEFAULT_PATH) if(ri) - #message(STATUS "info: 'find_file' in exepath/dirs (${ri})") + #message(STATUS "info: 'find_file' in exepath/rpaths/dirs (${ri})") set(resolved 1) set(resolved_item "${ri}") set(ri "ri-NOTFOUND") @@ -436,7 +442,7 @@ function(gp_resolve_item context item exepath dirs resolved_item_var) # by whatever logic they choose: # if(COMMAND gp_resolve_item_override) - gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved) + gp_resolve_item_override("${context}" "${item}" "${executable}" "${dirs}" resolved_item resolved) endif() if(NOT resolved) @@ -459,7 +465,7 @@ warning: cannot resolve item '${item}' # # context='${context}' # item='${item}' -# exepath='${exepath}' +# executable='${executable}' # dirs='${dirs}' # resolved_item_var='${resolved_item_var}' #****************************************************************************** @@ -470,7 +476,8 @@ warning: cannot resolve item '${item}' endfunction() -function(gp_resolved_file_type original_file file exepath dirs type_var) +function(gp_resolved_file_type original_file file executable dirs type_var) + set(rpaths "${ARGV5}") #message(STATUS "**") if(NOT IS_ABSOLUTE "${original_file}") @@ -489,7 +496,7 @@ function(gp_resolved_file_type original_file file exepath dirs type_var) if(NOT is_embedded) if(NOT IS_ABSOLUTE "${file}") - gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file) + gp_resolve_item("${original_file}" "${file}" "${executable}" "${dirs}" resolved_file "${rpaths}") endif() string(TOLOWER "${original_file}" original_lower) @@ -596,22 +603,23 @@ endfunction() function(gp_file_type original_file file type_var) + set(executable "${ARGV3}") + if(NOT IS_ABSOLUTE "${original_file}") message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file") endif() - get_filename_component(exepath "${original_file}" PATH) - set(type "") - gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type) + gp_resolved_file_type("${original_file}" "${file}" "${executable}" "" type) set(${type_var} "${type}" PARENT_SCOPE) endfunction() -function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs) +function(get_prerequisites target prerequisites_var exclude_system recurse executable dirs) set(verbose 0) set(eol_char "E") + set(rpaths "${ARGV6}") if(NOT IS_ABSOLUTE "${target}") message("warning: target '${target}' is not absolute...") @@ -738,6 +746,7 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa if("${gp_tool}" STREQUAL "ldd") set(old_ld_env "$ENV{LD_LIBRARY_PATH}") + get_filename_component(exepath "${executable}" PATH) set(new_ld_env "${exepath}") foreach(dir ${dirs}) set(new_ld_env "${new_ld_env}:${dir}") @@ -834,7 +843,7 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa if(add_item AND ${exclude_system}) set(type "") - gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type) + gp_resolved_file_type("${target}" "${item}" "${executable}" "${dirs}" type "${rpaths}") if("${type}" STREQUAL "system") set(add_item 0) @@ -855,7 +864,7 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa # that the analysis tools can simply accept it as input. # if(NOT list_length_before_append EQUAL list_length_after_append) - gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item) + gp_resolve_item("${target}" "${item}" "${executable}" "${dirs}" resolved_item "${rpaths}") set(unseen_prereqs ${unseen_prereqs} "${resolved_item}") endif() endif() @@ -874,7 +883,7 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa if(${recurse}) set(more_inputs ${unseen_prereqs}) foreach(input ${more_inputs}) - get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}") + get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${executable}" "${dirs}" "${rpaths}") endforeach() endif() @@ -911,7 +920,7 @@ function(list_prerequisites target) get_filename_component(exepath "${target}" PATH) set(prereqs "") - get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "") + get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${target}" "") if(print_target) message(STATUS "File '${target}' depends on:") -- 1.9.3 (Apple Git-50) -- Powered by www.kitware.com Please keep messages on-topic and check the CMake FAQ at: http://www.cmake.org/Wiki/CMake_FAQ Kitware offers various services to support the CMake community. For more information on each offering, please visit: CMake Support: http://cmake.org/cmake/help/support.html CMake Consulting: http://cmake.org/cmake/help/consulting.html CMake Training Courses: http://cmake.org/cmake/help/training.html Visit other Kitware open-source projects at http://www.kitware.com/opensource/opensource.html Follow this link to subscribe/unsubscribe: http://public.kitware.com/mailman/listinfo/cmake-developers