Building OSX App Bundle

MacosGccOtool

Macos Problem Overview


Suppose I have have made a an osX app without using Xcode. After compiling with GCC I get an executable which is linked to several other libraries. Some of those libraries might again be dynamically linked to other non-standard system libraries

Is there any tool which exists which makes an OSX App bundle by first making the required directory structures and then recursively copying/checking/fixing links to make sure all the dynamic dependencies are also in the app bundle?

I guess I can try writing something like this but I was wondering if something like this exists already.

Macos Solutions


Solution 1 - Macos

There are two ways to create an app bundle on MacOSX, the Easy and the Ugly.

The easy way is just to use XCode. Done.

The problem is sometimes you can't.

In my case I'm building an app that builds other apps. I can't assume the user has XCode installed. I'm also using http://www.macports.org/">MacPorts</a> to build the libraries my app depends on.
I need to make sure that these dylibs get bundled with the app before I distribute it.

Disclaimer: I'm totally unqualified to write this post, everything in is has been gleamed from Apple docs, picking apart existing apps and trial and error. It works for me, but is most likely wrong. Please [email protected]">email me if you have any corrections.

First thing you should know is that an app bundle is just a directory.
Let's examine the structure of a hypothetical foo.app.

foo.app/
Contents/
Info.plist
MacOS/
foo
Resources/
foo.icns

Info.plist is a plain XML file.
You can edit it with a text editor or the Property List Editor app that comes bundled with XCode. (It's in /Developer/Applications/Utilities/ directory).

The key things you need to include are:

CFBundleName - The name of the app.

CFBundleIcon - An Icon file assumed to be in Contents/Resources dir. Use the Icon Composer app to create the icon. (It's also in the /Developer/Applications/Utilities/ directory) You can just drag and drop a png onto it's window and should automatically generate the mip-levels for you.

CFBundleExecutable - Name of the executable file assumed to be in Contents/MacOS/ sub-folder.

There are lots more options, the ones listed above are only the bare minimum. Here's some Apple documentation on the http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ObjCTutorial/08Configuring/08Configuring.html">Info.plist</a> file and https://developer.apple.com/library/content/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html">App bundle structure.

Also, Here's a sample Info.plist.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleGetInfoString</key>
<string>Foo</string>
<key>CFBundleExecutable</key>
<string>foo</string>
<key>CFBundleIdentifier</key>
<string>com.your-company-name.www</string>
<key>CFBundleName</key>
<string>foo</string>
<key>CFBundleIconFile</key>
<string>foo.icns</string>
<key>CFBundleShortVersionString</key>
<string>0.01</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>IFMajorVersion</key>
<integer>0</integer>
<key>IFMinorVersion</key>
<integer>1</integer>
</dict>
</plist>

In a perfect world you could just drop your executable into the Contents/MacOS/ dir and be done. However, if your app has any non-standard dylib dependencies it won't work. Like Windows, MacOS comes with it's own special kind of http://en.wikipedia.org/wiki/DLL_hell">DLL Hell.

If you're using http://www.macports.org/">MacPorts</a> to build libraries that you link against, the locations of the the dylibs will be hard-coded into your executable. If you run the app on a machine that has the dylibs in the exact same location, it will run fine.
However, most users won't have them installed; when they double-click your app it will just crash.

Before you distribute you executable you'll need to collect all the dylibs it loads and copy them into the app bundle. You will also need to edit the executable so that it will look for the dylibs in the correct place. i.e. where you copied them to.

Hand editing an executable sounds dangerous right?
Luckily there are command line tools to help.

otool -L executable_name

This command will list all the dylibs that your app depends on. If you see any that are NOT in the System/Library or usr/lib folder, those are the ones you'll need to copy into the app bundle.
Copy them into the /Contents/MacOS/ folder. Next you'll need to edit the executable to use the new dylibs.

First, you need to make sure that you link using the -headerpad_max_install_names flag.
This just makes sure that if the new dylib path is longer then the previous one, there will be room for it.

Second, use the install_name_tool to change each dylib path.

install_name_tool -change existing_path_to_dylib @executable_path/blah.dylib executable_name

As a practical example, Let's say your app uses http://www.libsdl.org/">libSDL</a>;, and otool lists it's location as "/opt/local/lib/libSDL-1.2.0.dylib".

First copy it into the app bundle.

cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/

Then edit the executable to use the new location (NOTE: make sure you built it with the -headerpad_max_install_names flag)

install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @executable_path/libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo

Whew, we're almost done.
Now there's a small issue with the current working directory.

When you start your app the current directory will be the directory above where the application is located.
For example: If you place the foo.app in the /Applcations folder then the current directory when you launch the app will be the /Applications folder. Not the /Applications/foo.app/Contents/MacOS/ as you might expect.

You can alter your app to account for this, or you can use this magic little launcher script that will change the current directory and launch your app.

#!/bin/bash
cd "${0%/*}"
./foo

Make sure you adjust the Info.plist file so that CFBundleExecutable points to the launch script and not to the previous executable.

Ok, all done now.
Luckily, once you know all this stuff you bury it in a build script.

Solution 2 - Macos

I actually found a very handy tool that deserves some credit ... NO - I did not develop this ;)

https://github.com/auriamg/macdylibbundler/

It will resolve all the dependencies and "fix" your executable as well as your dylib files to work smoothly in your app bundle.

... it will also check for the dependencies of your dependent dynamic libs :D

Solution 3 - Macos

I use this in my Makefile... It creates an app bundle. Read it and understand it, because you'll need a png icon file in a macosx/ folder along with the PkgInfo and Info.plist files i include here...

"it works on my computer"... I use this for multiple apps on Mavericks...

APPNAME=MyApp
APPBUNDLE=$(APPNAME).app
APPBUNDLECONTENTS=$(APPBUNDLE)/Contents
APPBUNDLEEXE=$(APPBUNDLECONTENTS)/MacOS
APPBUNDLERESOURCES=$(APPBUNDLECONTENTS)/Resources
APPBUNDLEICON=$(APPBUNDLECONTENTS)/Resources
appbundle: macosx/$(APPNAME).icns
	rm -rf $(APPBUNDLE)
	mkdir $(APPBUNDLE)
	mkdir $(APPBUNDLE)/Contents
	mkdir $(APPBUNDLE)/Contents/MacOS
	mkdir $(APPBUNDLE)/Contents/Resources
	cp macosx/Info.plist $(APPBUNDLECONTENTS)/
	cp macosx/PkgInfo $(APPBUNDLECONTENTS)/
	cp macosx/$(APPNAME).icns $(APPBUNDLEICON)/
	cp $(OUTFILE) $(APPBUNDLEEXE)/$(APPNAME)

macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png
	rm -rf macosx/$(APPNAME).iconset
	mkdir macosx/$(APPNAME).iconset
	sips -z 16 16     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16.png
	sips -z 32 32     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/[email protected]
	sips -z 32 32     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32.png
	sips -z 64 64     macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/[email protected]
	sips -z 128 128   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128.png
	sips -z 256 256   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/[email protected]
	sips -z 256 256   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256.png
	sips -z 512 512   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/[email protected]
	sips -z 512 512   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png
	cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/[email protected]
	iconutil -c icns -o macosx/$(APPNAME).icns macosx/$(APPNAME).iconset
	rm -r macosx/$(APPNAME).iconset

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleExecutable</key>
    <string>MyApp</string>
    <key>CFBundleGetInfoString</key>
    <string>0.48.2, Copyright 2013 my company</string>
    <key>CFBundleIconFile</key>
    <string>MyApp.icns</string>
    <key>CFBundleIdentifier</key>
    <string>com.mycompany.MyApp</string>
    <key>CFBundleDocumentTypes</key>
    <array>
    </array>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>0.48.2</string>
    <key>CFBundleSignature</key>
    <string>MyAp</string>
    <key>CFBundleVersion</key>
    <string>0.48.2</string>
    <key>NSHumanReadableCopyright</key>
    <string>Copyright 2013 my company.</string>
    <key>LSMinimumSystemVersion</key>
    <string>10.3</string>
</dict>
</plist>

PkgInfo

APPLMyAp

Solution 4 - Macos

The simplest solution is: create once an Xcode project without changing anything (i.e. keep the simple one-window app that Xcode creates for you), build it, and copy the bundle it created for you. Then, edit the files (notably the Info.plist) to suit your content, and put your own binary in the Contents/MacOS/ directory.

Solution 5 - Macos

There are some open source tools to help build app bundles with dependent libraries for specific environments, for example, py2app for Python-based applications. If you don't find a more general one, perhaps you can adapt it to your needs.

Solution 6 - Macos

I wish I've found this post earlier....

Here's my sketchy way of solving this problem using a Run script phase which is invoked every time I build a Release version of my app:

# this is an array of my dependencies' libraries paths 
# which will be iterated in order to find those dependencies using otool -L
libpaths=("$NDNRTC_LIB_PATH" "$BOOST_LIB_PATH" "$NDNCHAT_LIB_PATH" "$NDNCPP_LIB_PATH" "/opt/local/lib")
frameworksDir=$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH
executable=$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH

#echo "libpaths $libpaths"
bRecursion=0
lRecursion=0

# this function iterates through libpaths array
# and checks binary with "otool -L" command for containment
# of dependency which has "libpath" path
# if such dependency has been found, it will be copied to Frameworks 
# folder and binary will be fixed with "install_name_tool -change" command
# to point to Frameworks/<dependency> library
# then, dependency is checked recursively with resolveDependencies function
function resolveDependencies()
{
    local binfile=$1
    local prefix=$2
    local binname=$(basename $binfile)
    local offset=$((lRecursion*20))
    printf "%s :\t%s\n" $prefix "resolving $binname..."
    
    for path in ${libpaths[@]}; do
        local temp=$path
        #echo "check lib path $path"
        local pattern="$path/([A-z0-9.-]+\.dylib)"
        while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do
            local libname=${BASH_REMATCH[1]}
            otool -L ${binfile}
            #echo "found match $libname"
            printf "%s :\t%s\n" $prefix "fixing $libname..."
            local libpath="${path}/$libname"
            #echo "cp $libpath $frameworksDir"
            ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami)
            local installLibPath="@rpath/$libname"
            #echo "install_name_tool -change $libpath $installLibPath $binfile"
            if [ "$libname" == "$binname" ]; then
                install_name_tool -id "@rpath/$libname" $binfile
                printf "%s :\t%s\n" $prefix "fixed id for $libname."
            else
                install_name_tool -change $libpath $installLibPath $binfile
                printf "%s :\t%s\n" $prefix "$libname dependency resolved."
                let lRecursion++
                resolveDependencies "$frameworksDir/$libname" "$prefix>$libname"
                resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname"
                let lRecursion--
            fi
            path=$temp
        done # while
    done # for
    
    printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved."
} # resolveDependencies

# for some reason, unlike other dependencies which maintain full path
# in "otool -L" output, boost libraries do not - they just appear 
# as "libboost_xxxx.dylib" entries, without fully qualified path
# thus, resolveDependencies can't be used and a designated function is needed
# this function works pretty much in a similar way to resolveDependencies
# but targets only dependencies starting with "libboost_", copies them
# to the Frameworks folder and resolves them recursively
function resolveBoostDependencies()
{
    local binfile=$1
    local prefix=$2
    local binname=$(basename $binfile)
    local offset=$(((bRecursion+lRecursion)*20))
    printf "%s :\t%s\n" $prefix "resolving Boost for $(basename $binfile)..."

    local pattern="[[:space:]]libboost_([A-z0-9.-]+\.dylib)"
    while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do
        local libname="libboost_${BASH_REMATCH[1]}"
        #echo "found match $libname"
        local libpath="${BOOST_LIB_PATH}/$libname"
        #echo "cp $libpath $frameworksDir"
        ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami)
        installLibPath="@rpath/$libname"
        #echo "install_name_tool -change $libname $installLibPath $binfile"
        if [ "$libname" == "$binname" ]; then
            install_name_tool -id "@rpath/$libname" $binfile
            printf "%s :\t%s\n" $prefix "fixed id for $libname."
        else
            install_name_tool -change $libname $installLibPath $binfile
            printf "%s :\t%s\n" $prefix "$libname Boost dependency resolved."
            let bRecursion++
            resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname"
            let bRecursion--
        fi
    done # while
    
    printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved."
}

resolveDependencies $executable $(basename $executable)
resolveBoostDependencies $executable $(basename $executable)

Hope this might be useful to someone.

Solution 7 - Macos

A workaround to have the menu work on Mac with wxWidget code is to simply:

  1. Start the program in a terminal as usual (./appname)
  2. The program GUI starts as normal, and click the terminal to have the app lose focus
  3. Click the GUI to regain focus and the menu items work.

I agree that an app bundle is the correct way to build a program on a Mac. This is just a simple workaround for help during debugging.

Edit: This is on Mac Catalina, wxWidgets 3.1.4, with g++ 4.2.1 (Nov 2020)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYogiView Question on Stackoverflow
Solution 1 - MacoshyperlogicView Answer on Stackoverflow
Solution 2 - MacosChrisView Answer on Stackoverflow
Solution 3 - MacossubatomicView Answer on Stackoverflow
Solution 4 - MacosF'xView Answer on Stackoverflow
Solution 5 - MacosNed DeilyView Answer on Stackoverflow
Solution 6 - MacospeetonnView Answer on Stackoverflow
Solution 7 - MacoseugenedakinView Answer on Stackoverflow