In the end, this did not end up as easy as I originally thought. It works but there are caveats.
Building on the answers from Andon M. Coleman & Alexander Klimetschek, here is a script function which takes dylib names from Homebrew, copies them into the app bundle's Contents/Frameworks
directory, and sets the executable's corresponding search path using install_name_tool
. I noticed that sometimes the actual .dylib location is different from what the Xcode linker finds when putting together the executable and this handles that as well by querying the exe with otool -L
.
The script below copies the .dylib for the liblo library into the app bundle. Adding new libs should be as simple as adding a new copy lib ###.dlyib
line to the bottom of the script.
# exit on error
set -e
# paths
LOCALLIBS_PATH="$SRCROOT/libs"
HOMEBREW_PATH="/usr/local"
FRAMEWORKS_PATH="$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH"
EXE_PATH="$BUILT_PRODUCTS_DIR/$EXECUTABLE_FOLDER_PATH/$EXECUTABLE_NAME"
# code signing identity
IDENTITY="$EXPANDED_CODE_SIGN_IDENTITY_NAME"
if [ "$IDENTITY" == "" ] ; then
IDENTITY="$CODE_SIGN_IDENTITY"
fi
# copy lib into Contents/Frameworks and set lib search path
# $1: library .dylib
# $2: optional path to libs folder, otherwise use Homebrew
copylib() {
LIB=$1
if [ "$2" == "" ] ; then
# use homebrew
LIB_PATH=$(find "$HOMEBREW_PATH" -type f -name $LIB)
else
# use given path
LIB_PATH="$2/$LIB"
fi
echo "$LIB:"
echo " $LIB_PATH -> $FRAMEWORKS_FOLDER_PATH/$LIB"
# copy lib, set permissions, and sign
mkdir -p "$FRAMEWORKS_PATH"
cp "$LIB_PATH" "$FRAMEWORKS_PATH/"
chmod 755 "$FRAMEWORKS_PATH/$LIB"
codesign --verify --force --sign "$IDENTITY" "$FRAMEWORKS_PATH/$LIB"
# set new path within executable
# note: grep --max 1 as multi-arch builds will print path once *per arch*
OLD=$(otool -L "$EXE_PATH" | grep --max 1 $LIB | awk '{print $1}')
NEW="@executable_path/../Frameworks/$LIB"
install_name_tool -change "$OLD" "$NEW" "$EXE_PATH"
# print to confirm new path
otool -L "$EXE_PATH" | grep $LIB
}
##### GO
# use lib from homebrew
copy lib liblo.7.dylib
# or use local lib
#copylib liblo.7.dylib "$LOCALLIBS_PATH/liblo/lib"
Add this as a Run Script build phase in the Xcode project. You can also save it as an external file and call it using sh
within the Run Script phase with:
sh $PROJECT_DIR/path-to/copy-libs.sh
Example build report output:
liblo.7.dylib:
/usr/local/Cellar/liblo/0.29/lib/liblo.7.dylib ->> YOURAPP.app/Contents/Frameworks/liblo.7.dylib
@executable_path/../Frameworks/liblo.7.dylib (compatibility version 11.0.0, current version 11.0.0)
The $(find $HOMEBREW_PATH -type f -name $LIB)
could probably be improved by following the .dylib symlink in /usr/local/lib
instead.
UPDATE: You will probably also need to make sure the new dylibs are signed otherwise the code signing step will fail when building, at least it eventually did for me. Following this SO post, adding --deep
to the Other Code Signing Flags option in the project Build Settings works.
There might be a better way to sign only the files that the script adds, but I didn't feel like digging into running codesign
manually. From what I've read, however, --deep
is not suggested by Apple for anything but temporary fixes, so it's probably not a best practice.
UPDATE 2: Running --deep
didn't actually solve the problem and the app still would not run on other machines. In the end, I needed to code sign the copied .dylib manually which turned out to be relatively easy.
One note: I ran into an ambiguous "Mac Developer" identities error when running codesign which seemed to be confused by two certificates with the same name in the Keychain: ie. the current developer certificate and the previous expired developer certificate. Opening Keychain Access and deleting the expired certificate solved the problem.
UPDATE 3: Quoted path variable usage to fix paths with spaces.
UPDATE 4: This solution works fine for building apps for recent systems, but I ran into a code signing problem when running the app on macOS 10.10. Judging from this SO post, older macOS versions use a different codesign hash algorithm and the app would run fine on a 10.12 system but fail with a code signing error on 10.10. On both systems, codesign verified that everything was signed correctly.
You basically need to build the libraries with the -mmacosx-version-min
set so codesign will be able to tell which algorithms to use to support the min and the recent macOS versions. I tried to pass custom CFLAGS/LDFLAGS to Homebrew while building liblo from source, but it's not possible without editing the brew formula. In the end, I decided to write a build script to build liblo from source myself and I modified the copy script so it can also take a local location. Now everything is finally working.
TLDR: Homebrew libs work great for initial development & testing, but building the libs manually works better for deployment.
install_name_tool -change
to correct this issue. – Belkinstall_name_tool -change
on a binary in OS X, it is used to change how .dylib files are located when run from a .app bundle. Usually it involves something like@executable_path/../Frameworks/MyLib.dylib
. But if you are using the Xcode GUI, it should be doing this for you when you bundle your .app. Have a look here. This has been discussed on SO as well, but I cannot find an actual question at the moment. – Belk