From d18afb982f2dc1f9e02c1969087024456a2e8cb8 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Thu, 27 Oct 2016 22:06:06 -0700 Subject: [PATCH] support for fully independent .app build on Mac The resulting Mac wX .app build is now completely independent and redistributable, only needs to be codesigned. Necessary dylibs are bundled and linked in a POST_BUILD step using third_party_libs_tool (included) for which I created a separate repo here as well: http://github.com/rkitover/mac-third-party-libs-tool Turn off Cairo on Mac because it does not work for now. Set RPATH on the executable to @loader_path/../Frameworks, the bundling tool also does this. Update .gitignore for Finder .DS_Store files. TOOD: * write a ./quickbuild for Mac and other platforms such as msys2 and linux --- .gitignore | 3 + src/wx/CMakeLists.txt | 19 +- src/wx/tools/osx/third_party_libs_tool | 262 +++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 1 deletion(-) create mode 100755 src/wx/tools/osx/third_party_libs_tool diff --git a/.gitignore b/.gitignore index 539471f9..9cd04a35 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ build/ # vim swap files *.sw? + +# mac finder crap +*.DS_Store diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index 027c2c1e..9821028a 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -1,8 +1,17 @@ #Do not use this file directly. Always use the top level CMakeLists.txt file # This build is much easier if we just do it here. SET( CMAKE_CXX_FLAGS -std=gnu++11 ) + # not yet implemented -option( ENABLE_CAIRO "Enable Cairo rendering for the wxWidgets port" ON ) +IF(APPLE) + # does not work, no reason to link to it + SET(CAIRO_DEFAULT OFF) +ELSE(APPLE) + SET(CAIRO_DEFAULT ON) +ENDIF(APPLE) + +option(ENABLE_CAIRO "Enable Cairo rendering for the wxWidgets port" ${CAIRO_DEFAULT}) + if( WIN32 ) # not yet implemented option( ENABLE_DIRECT3D "Enable Direct3D rendering for the wxWidgets port" ON ) @@ -201,6 +210,11 @@ SET(VBAM_ICON vbam.icns) SET(VBAM_ICON_PATH ${CMAKE_CURRENT_SOURCE_DIR}/icons/${VBAM_ICON}) +IF(APPLE) + SET(CMAKE_BUILD_WITH_INSTALL_RPATH ON) + SET(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks") +ENDIF(APPLE) + ADD_EXECUTABLE ( visualboyadvance-m WIN32 @@ -232,6 +246,9 @@ if(APPLE) SET_PROPERTY(TARGET visualboyadvance-m APPEND PROPERTY MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/wxplist.in) SET(MACOSX_BUNDLE_ICON_FILE ${VBAM_ICON}) SET_SOURCE_FILES_PROPERTIES(${VBAM_ICON_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + ADD_CUSTOM_COMMAND(TARGET visualboyadvance-m POST_BUILD + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tools/osx/third_party_libs_tool "$/../..") endif(APPLE) SET(WX_EXE_NAME visualboyadvance-m-wx${CMAKE_EXECUTABLE_SUFFIX}) diff --git a/src/wx/tools/osx/third_party_libs_tool b/src/wx/tools/osx/third_party_libs_tool new file mode 100755 index 00000000..c3f8c85d --- /dev/null +++ b/src/wx/tools/osx/third_party_libs_tool @@ -0,0 +1,262 @@ +#!/bin/sh + +version=0.1 + +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 | sed 's,/*$,,') + + 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 + OLDIFS=$IFS + IFS=' +' + path= + 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 + + echo "$path" + done +} + +resolve_link() { + file="$1" + + while [ -h "$file" ]; do + ls0=`ls -l "$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 + ) + + if [ -n "$lib_link_path" ]; then + install_name_tool -change "$lib_link_path" "@rpath/$lib_basename" "$target" + fi +} + +# 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 "$@"