#!/bin/sh version=0.6 main() { # parse options list= while [ $# -gt 0 ]; do case "$1" in -h|--help|--usage) usage quit 0 ;; -v|--version) echo "third_party_libs_tool $version" quit 0 ;; -l|--list) list=1 shift ;; *) break ;; esac done if [ $# -ne 1 ]; then usage quit 1 fi mktmp app_bundle=$(echo "$1" | fully_resolve_links) case "$app_bundle" in *.app|*.APP) if [ ! -d "$app_bundle" ]; then usage quit 1 fi ;; *) usage quit 1 ;; esac set -- OLDIFS=$IFS IFS=' ' for file in $(find "$app_bundle/Contents/MacOS" -type f); do case "$file" in *.dylib) set -- "$@" "$file" ;; *) [ -x "$file" ] && set -- "$@" "$file" ;; esac done IFS=$OLDIFS frameworks="$app_bundle/Contents/Frameworks" mkdir -p "$frameworks" 2>/dev/null lib_scan "$@" | fully_resolve_links | sort -u | \ while read lib; do if [ -n "$list" ]; then echo "$lib" else cp -f "$lib" "$frameworks" 2>/dev/null fi done # fix dynamic link info in executables and just copied libs [ -z "$list" ] && relink_all "$@" quit 0 } usage() { cat <<'EOF' Usage: third_party_libs_tool [OPTION] BUNDLE.app Bundle third party dylibs into BUNDLE.app and fix up linkages. Binaries are searched for in BUNDLE.app/Contents/MacOS . The dylibs are copied into BUNDLE.app/Contents/Frameworks . -h, --help, --usage Show this help screen and exit. -v, --version Show version information and exit. -l, --list Only list dylibs used by binaries, do not copy or link. Examples: third_party_libs_tool ./MyApp.app # bundle and link ./MyApp.app third_party_libs_tool --list ./MyApp.app # list third party libs used by ./MyApp.app Project homepage and documentation: <http://github.com/rkitover/mac-third-party-libs-tool> EOF } mktmp() { tmp="/tmp/third_party_libs_tool_$$" mkdir "$tmp" || quit 1 chmod 700 "$tmp" 2>/dev/null trap "quit 1" PIPE HUP INT QUIT ILL TRAP KILL BUS TERM } quit() { [ -n "$tmp" ] && rm -rf "$tmp" 2>/dev/null exit ${1:-0} } lib_scan() { for bin in "$@"; do case "$bin" in *.dylib) ;; *) [ ! -x "$bin" ] && continue ;; esac set -- OLDIFS=$IFS IFS=' ' for lib in $(otool -L "$bin" 2>/dev/null | sed -n 's/^ \([^ ]*\).*/\1/p' | grep -v '^/System/' | grep -v '^/usr/lib/'); do [ "$lib" = "$bin" ] && continue echo "$lib" set -- "$@" "$lib" done IFS=$OLDIFS # recurse [ $# -ne 0 ] && lib_scan "$@" done } fully_resolve_links() { while read -r file; do # get initial part for non-absolute path, or blank for absolute path=${file%%/*} # and set $file to the rest file=${file#*/} OLDIFS=$IFS IFS=' ' for part in $(echo "$file" | sed 's,^/*,,; s,/*$,,; s,//*,\ ,g'); do path=$(resolve_link "$path/$part") done IFS=$OLDIFS # remove '..' path parts while :; do case "$path" in */../*|*/..) path=$(echo "$path" | sed 's,//*[^/][^/]*//*\.\./*,/,g') ;; *) break ;; esac done # remove trailing /s path=$(echo "$path" | sed 's,/*$,,') echo "$path" done } resolve_link() { file="$1" while [ -h "$file" ]; do ls0=`ls -ld "$file"` new_link=`expr "$ls0" : '.* -> \(.*\)$'` if expr "$new_link" : '/.*' > /dev/null; then file="$new_link" else file="${file%/*}"/"$new_link" fi done echo "$file" } relink_all() { for exe in "$@"; do # dylib search path for executable install_name_tool -add_rpath '@loader_path/../Frameworks' "$exe" OLDIFS=$IFS IFS=' ' set -- for lib in $(find "$app_bundle/Contents/Frameworks" -name '*.dylib'); do set -- "$@" "$lib" done IFS=$OLDIFS for lib in "$@"; do # make lib writable chmod u+w "$lib" # change id of lib install_name_tool -id "@rpath/${lib##*/}" "$lib" # set search path of lib install_name_tool -add_rpath '@loader_path/../Frameworks' "$lib" # relink executable and all other libs to this lib for target in "$exe" "$@"; do relink "$lib" "$target" done done done } relink() { lib=$1 target=$2 lib_basename=${lib##*/} lib_basename_unversioned_re=$(echo "$lib_basename" | sed 's/[0-9.-]*\.dylib$//; s/\./\\./g') # remove full path and version of lib in executable lib_link_path=$( otool -l "$target" 2>/dev/null | \ sed -n 's,^ *name \(/.*/*'"$lib_basename_unversioned_re"'[0-9.-]*\.dylib\) (offset .*,\1,p' | \ head -1 ) [ -z "$lib_link_path" ] && return 0 # check that the shorter basename is the prefix of the longer basename # that is, the lib versions match lib1=${lib_basename%.dylib} lib2=${lib_link_path##*/} lib2=${lib2%.dylib} if [ "${#lib1}" -le "${#lib2}" ]; then shorter=$lib1 longer=$lib2 else shorter=$lib2 longer=$lib1 fi case "$longer" in "$shorter"*) # and if so, relink target to the lib install_name_tool -change "$lib_link_path" "@rpath/$lib_basename" "$target" ;; esac } # try with sudo in case it fails, # also suppress duplicate path errors install_name_tool() { out_file="$tmp/install_name_tool.out" if ! command install_name_tool "$@" >"$out_file" 2>&1; then if grep -Eq -i 'permission denied|bad file descriptor' "$out_file"; then if ! command sudo install_name_tool "$@" >"$out_file" 2>&1; then cat "$out_file" >&2 return 1 fi elif ! grep -Eq -i 'would duplicate path' "$out_file"; then cat "$out_file" >&2 return 1 fi fi return 0 } main "$@"