Overhaul GetPrerequisites and BundleUtilities: make fixup_bundle do something useful on Windows and Linux.

Formerly, fixup_bundle was useful only on the Mac for making standalone bundle applications that could be drag-n-drop moved to anyplace in the file system. fixup_bundle is not just for the Mac any more. It will now analyze executable files on Windows and Linux, too, and copy necessary non-system dlls to the same folder that the executable is in. This should work with dlls that you build as part of your build and also with 3rd-party dlls as long as you give fixup_bundle the right list of directories to search for those dlls. Many thanks to Clinton Stimpson for his help in ironing out the details involved in making this work.
This commit is contained in:
David Cole 2009-08-05 14:59:14 -04:00
parent 80f0201b37
commit fe0b121da9
2 changed files with 264 additions and 132 deletions

View File

@ -174,14 +174,18 @@ function(get_bundle_and_executable app bundle_var executable_var valid_var)
is_file_executable("${app}" is_executable)
if(is_executable)
get_dotapp_dir("${app}" dotapp_dir)
if(EXISTS "${dotapp_dir}" AND EXISTS "${app}")
if(EXISTS "${dotapp_dir}")
set(${bundle_var} "${dotapp_dir}" PARENT_SCOPE)
set(${executable_var} "${app}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled executable file case...")
else(EXISTS "${dotapp_dir}" AND EXISTS "${app}")
message(STATUS "warning: *NOT* handled - executable file case...")
endif(EXISTS "${dotapp_dir}" AND EXISTS "${app}")
#message(STATUS "info: handled executable file in .app dir case...")
else()
get_filename_component(app_dir "${app}" PATH)
set(${bundle_var} "${app_dir}" PARENT_SCOPE)
set(${executable_var} "${app}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled executable file in any dir case...")
endif()
else(is_executable)
message(STATUS "warning: *NOT* handled - not .app dir, not executable file...")
endif(is_executable)
@ -230,6 +234,9 @@ endfunction(get_bundle_all_executables)
#
function(get_item_key item key_var)
get_filename_component(item_name "${item}" NAME)
if(WIN32)
string(TOLOWER "${item_name}" item_name)
endif()
string(REGEX REPLACE "\\." "_" ${key_var} "${item_name}")
set(${key_var} ${${key_var}} PARENT_SCOPE)
endfunction(get_item_key)
@ -388,15 +395,25 @@ endfunction(get_bundle_keys)
# copy_resolved_item_into_bundle
#
# Copy a resolved item into the bundle if necessary. Copy is not necessary if the resolved_item
# is the same as the resolved_embedded_item.
# Copy a resolved item into the bundle if necessary. Copy is not necessary if
# the resolved_item is "the same as" the resolved_embedded_item.
#
function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
if("${resolved_item}" STREQUAL "${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("${resolved_item}" STREQUAL "${resolved_embedded_item}")
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}")
endif("${resolved_item}" STREQUAL "${resolved_embedded_item}")
endif()
endfunction(copy_resolved_item_into_bundle)
@ -503,9 +520,12 @@ function(fixup_bundle app libs dirs)
message(STATUS "fixup_bundle: fixing...")
foreach(key ${keys})
math(EXPR i ${i}+1)
message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'")
#message(STATUS " exepath='${exepath}'")
fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}")
if(APPLE)
message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'")
fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}")
else(APPLE)
message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'")
endif(APPLE)
endforeach(key)
message(STATUS "fixup_bundle: cleaning up...")
@ -514,7 +534,7 @@ function(fixup_bundle app libs dirs)
message(STATUS "fixup_bundle: verifying...")
verify_app("${app}")
else(valid)
message(STATUS "error: fixup_bundle: not a valid bundle")
message(SEND_ERROR "error: fixup_bundle: not a valid bundle")
endif(valid)
message(STATUS "fixup_bundle: done")
@ -550,30 +570,42 @@ function(verify_bundle_prerequisites bundle result_var info_var)
is_file_executable("${f}" is_executable)
if(is_executable)
get_filename_component(exepath "${f}" PATH)
message(STATUS "executable file: ${f}")
math(EXPR count "${count} + 1")
message(STATUS "executable file ${count}: ${f}")
set(prereqs "")
get_prerequisites("${f}" prereqs 1 1 "${exepath}" "")
# 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)
if (NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system")
set(external_prereqs ${external_prereqs} "${p}")
endif (NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system")
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()
endif()
endforeach(p)
if(external_prereqs)
# Found non-system/non-embedded prerequisites:
# Found non-system/somehow-unacceptable prerequisites:
set(result 0)
set(info ${info} "non-system/non-embedded prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n")
set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n")
endif(external_prereqs)
endif(is_executable)
endforeach(f)

View File

@ -10,12 +10,13 @@
#
# The following functions are provided by this script:
# gp_append_unique
# gp_file_type
# is_file_executable
# gp_item_default_embedded_path
# (projects can override with gp_item_default_embedded_path_override)
# gp_resolve_item
# (projects can override with gp_resolve_item_override)
# gp_resolved_file_type
# gp_file_type
# get_prerequisites
# list_prerequisites
# list_prerequisites_by_glob
@ -45,83 +46,6 @@ function(gp_append_unique list_var value)
endfunction(gp_append_unique)
# gp_file_type original_file file type_var
#
# Return the type of ${file} with respect to ${original_file}. String
# describing type of prerequisite is returned in variable named ${type_var}.
#
# Possible types are:
# system
# local
# embedded
# other
#
function(gp_file_type original_file file type_var)
set(is_embedded 0)
set(is_local 0)
set(is_system 0)
string(TOLOWER "${original_file}" original_lower)
string(TOLOWER "${file}" lower)
if("${file}" MATCHES "^@(executable|loader)_path")
set(is_embedded 1)
endif("${file}" MATCHES "^@(executable|loader)_path")
if(NOT is_embedded)
if(UNIX)
if("${file}" MATCHES "^(/lib/|/lib32/|/lib64/)")
set(is_system 1)
endif("${file}" MATCHES "^(/lib/|/lib32/|/lib64/)")
endif(UNIX)
if(APPLE)
if("${file}" MATCHES "^(/System/Library/|/usr/lib/)")
set(is_system 1)
endif("${file}" MATCHES "^(/System/Library/|/usr/lib/)")
endif(APPLE)
if(WIN32)
string(TOLOWER "$ENV{SystemRoot}" sysroot)
string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}")
string(TOLOWER "$ENV{windir}" windir)
string(REGEX REPLACE "\\\\" "/" windir "${windir}")
if("${lower}" MATCHES "^(${sysroot}/system|${windir}/system|msvc[^/]+dll)")
set(is_system 1)
endif("${lower}" MATCHES "^(${sysroot}/system|${windir}/system|msvc[^/]+dll)")
endif(WIN32)
if(NOT is_system)
get_filename_component(original_path "${original_lower}" PATH)
get_filename_component(path "${lower}" PATH)
if("${original_path}" STREQUAL "${path}")
set(is_local 1)
endif("${original_path}" STREQUAL "${path}")
endif(NOT is_system)
endif(NOT is_embedded)
# Return type string based on computed booleans:
#
set(type "other")
if(is_system)
set(type "system")
else(is_system)
if(is_embedded)
set(type "embedded")
else(is_embedded)
if(is_local)
set(type "local")
endif(is_local)
endif(is_embedded)
endif(is_system)
set(${type_var} "${type}" PARENT_SCOPE)
endfunction(gp_file_type)
# is_file_executable file result_var
#
# Return 1 in ${result_var} if ${file} is a binary executable.
@ -204,39 +128,49 @@ endfunction(is_file_executable)
# gp_item_default_embedded_path_override function.
#
function(gp_item_default_embedded_path item default_embedded_path_var)
#
# The assumption here is that all executables in the bundle will be
# in same-level-directories inside the bundle. The parent directory
# of an executable inside the bundle should be MacOS or a sibling of
# MacOS and all embedded paths returned from here will begin with
# "@executable_path/../" and will work from all executables in all
# such same-level-directories inside the bundle.
#
# By default, embed things right next to the main bundle executable:
# On Windows and Linux, "embed" prerequisites in the same directory
# as the executable by default:
#
set(path "@executable_path/../../Contents/MacOS")
set(path "@executable_path")
set(overridden 0)
# Embed .dylibs right next to the main bundle executable:
# On the Mac, relative to the executable depending on the type
# of the thing we are embedding:
#
if(item MATCHES "\\.dylib$")
set(path "@executable_path/../MacOS")
set(overridden 1)
endif(item MATCHES "\\.dylib$")
if(APPLE)
#
# The assumption here is that all executables in the bundle will be
# in same-level-directories inside the bundle. The parent directory
# of an executable inside the bundle should be MacOS or a sibling of
# MacOS and all embedded paths returned from here will begin with
# "@executable_path/../" and will work from all executables in all
# such same-level-directories inside the bundle.
#
# Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS):
#
if(NOT overridden)
if(item MATCHES "[^/]+\\.framework/")
set(path "@executable_path/../Frameworks")
# By default, embed things right next to the main bundle executable:
#
set(path "@executable_path/../../Contents/MacOS")
# Embed .dylibs right next to the main bundle executable:
#
if(item MATCHES "\\.dylib$")
set(path "@executable_path/../MacOS")
set(overridden 1)
endif(item MATCHES "[^/]+\\.framework/")
endif(NOT overridden)
endif(item MATCHES "\\.dylib$")
# Provide a hook so that projects can override the default embedded location of
# any given library by whatever logic they choose:
# Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS):
#
if(NOT overridden)
if(item MATCHES "[^/]+\\.framework/")
set(path "@executable_path/../Frameworks")
set(overridden 1)
endif(item MATCHES "[^/]+\\.framework/")
endif(NOT overridden)
endif()
# Provide a hook so that projects can override the default embedded location
# of any given library by whatever logic they choose:
#
if(COMMAND gp_item_default_embedded_path_override)
gp_item_default_embedded_path_override("${item}" path)
@ -276,7 +210,7 @@ function(gp_resolve_item context item exepath dirs resolved_item_var)
set(resolved 1)
set(resolved_item "${ri}")
else(EXISTS "${ri}")
message(STATUS "info: embedded item does not exist '${ri}'")
message(STATUS "warning: embedded item does not exist '${ri}'")
endif(EXISTS "${ri}")
endif(item MATCHES "@executable_path")
endif(NOT resolved)
@ -296,16 +230,17 @@ function(gp_resolve_item context item exepath dirs resolved_item_var)
set(resolved 1)
set(resolved_item "${ri}")
else(EXISTS "${ri}")
message(STATUS "info: embedded item does not exist '${ri}'")
message(STATUS "warning: embedded item does not exist '${ri}'")
endif(EXISTS "${ri}")
endif(item MATCHES "@loader_path")
endif(NOT resolved)
if(NOT resolved)
set(ri "ri-NOTFOUND")
find_file(ri "${item}" ${dirs})
find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH)
find_file(ri "${item}" ${exepath} ${dirs} /usr/lib)
if(ri)
#message(STATUS "info: found item in dirs (${ri})")
#message(STATUS "info: 'find_file' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
@ -321,7 +256,7 @@ function(gp_resolve_item context item exepath dirs resolved_item_var)
"/System/Library/Frameworks"
)
if(fw)
#message(STATUS "info: found framework (${fw})")
#message(STATUS "info: 'find_file' found framework (${fw})")
set(resolved 1)
set(resolved_item "${fw}")
set(fw "fw-NOTFOUND")
@ -335,8 +270,10 @@ function(gp_resolve_item context item exepath dirs resolved_item_var)
if(WIN32)
if(NOT resolved)
set(ri "ri-NOTFOUND")
find_program(ri "${item}" PATHS "${dirs}")
find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH)
find_program(ri "${item}" PATHS "${exepath};${dirs}")
if(ri)
#message(STATUS "info: 'find_program' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
@ -352,13 +289,164 @@ function(gp_resolve_item context item exepath dirs resolved_item_var)
endif(COMMAND gp_resolve_item_override)
if(NOT resolved)
message(STATUS "warning: cannot resolve item '${item}'")
message(STATUS "
warning: cannot resolve item '${item}'
possible problems:
need more directories?
need to use InstallRequiredSystemLibraries?
run in install tree instead of build tree?
")
# message(STATUS "
#******************************************************************************
#warning: cannot resolve item '${item}'
#
# possible problems:
# need more directories?
# need to use InstallRequiredSystemLibraries?
# run in install tree instead of build tree?
#
# context='${context}'
# item='${item}'
# exepath='${exepath}'
# dirs='${dirs}'
# resolved_item_var='${resolved_item_var}'
#******************************************************************************
#")
endif(NOT resolved)
set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE)
endfunction(gp_resolve_item)
# gp_resolved_file_type original_file file exepath dirs type_var
#
# 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}
# values -- but only for non-embedded items.
#
# Possible types are:
# system
# local
# embedded
# other
#
function(gp_resolved_file_type original_file file exepath dirs type_var)
#message(STATUS "**")
if(NOT IS_ABSOLUTE "${original_file}")
message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file")
endif()
set(is_embedded 0)
set(is_local 0)
set(is_system 0)
set(resolved_file "${file}")
if("${file}" MATCHES "^@(executable|loader)_path")
set(is_embedded 1)
endif()
if(NOT is_embedded)
if(NOT IS_ABSOLUTE "${file}")
gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file)
endif()
string(TOLOWER "${original_file}" original_lower)
string(TOLOWER "${resolved_file}" lower)
if(UNIX)
if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11R6/)")
set(is_system 1)
endif()
endif()
if(APPLE)
if(resolved_file MATCHES "^(/System/Library/|/usr/lib/)")
set(is_system 1)
endif()
endif()
if(WIN32)
string(TOLOWER "$ENV{SystemRoot}" sysroot)
string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}")
string(TOLOWER "$ENV{windir}" windir)
string(REGEX REPLACE "\\\\" "/" windir "${windir}")
if(lower MATCHES "^(${sysroot}/system|${windir}/system|(.*/)*msvc[^/]+dll)")
set(is_system 1)
endif()
endif()
if(NOT is_system)
get_filename_component(original_path "${original_lower}" PATH)
get_filename_component(path "${lower}" PATH)
if("${original_path}" STREQUAL "${path}")
set(is_local 1)
endif()
endif()
endif()
# Return type string based on computed booleans:
#
set(type "other")
if(is_system)
set(type "system")
elseif(is_embedded)
set(type "embedded")
elseif(is_local)
set(type "local")
endif()
#message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'")
#message(STATUS " type: '${type}'")
if(NOT is_embedded)
if(NOT IS_ABSOLUTE "${resolved_file}")
if(lower MATCHES "^msvc[^/]+dll" AND is_system)
message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'")
else()
message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect")
endif()
endif()
endif()
set(${type_var} "${type}" PARENT_SCOPE)
#message(STATUS "**")
endfunction()
# gp_file_type original_file file type_var
#
# Return the type of ${file} with respect to ${original_file}. String
# describing type of prerequisite is returned in variable named ${type_var}.
#
# Possible types are:
# system
# local
# embedded
# other
#
function(gp_file_type original_file file type_var)
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)
set(${type_var} "${type}" PARENT_SCOPE)
endfunction(gp_file_type)
# get_prerequisites target prerequisites_var exclude_system recurse dirs
#
# Get the list of shared library files required by ${target}. The list in
@ -477,6 +565,14 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
#
# </setup-gp_tool-vars>
if("${gp_tool}" STREQUAL "ldd")
set(old_ld_env "$ENV{LD_LIBRARY_PATH}")
foreach(dir ${exepath} ${dirs})
set(ENV{LD_LIBRARY_PATH} "${dir}:$ENV{LD_LIBRARY_PATH}")
endforeach(dir)
endif("${gp_tool}" STREQUAL "ldd")
# Track new prerequisites at each new level of recursion. Start with an
# empty list at each level:
#
@ -489,6 +585,10 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
OUTPUT_VARIABLE gp_cmd_ov
)
if("${gp_tool}" STREQUAL "ldd")
set(ENV{LD_LIBRARY_PATH} "${old_ld_env}")
endif("${gp_tool}" STREQUAL "ldd")
if(verbose)
message(STATUS "<RawOutput cmd='${gp_cmd} ${gp_cmd_args} ${target}'>")
message(STATUS "gp_cmd_ov='${gp_cmd_ov}'")
@ -535,7 +635,7 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
if(${exclude_system})
set(type "")
gp_file_type("${target}" "${item}" type)
gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type)
if("${type}" STREQUAL "system")
set(add_item 0)