Monday, June 7, 2010

Firebird embedded - OS X

Deploying an embedded Firebird database in an OS X bundle is similar to deploying it for Linux, but there are more problems to solve. This might not be the best solution, but it works so it can be at least a good starting point.

First I copied the files that I needed from the Firebird Framework folders to another folder. For example, let's call it /Users/fulvio/firebird_runtime.
Then I renamed the Firebird file to libfbembed.dylib: this is probably not necessary but I feel better using the same filename as in other platforms.

Files structure

I create the application as a bundle. The executable file is stored in the folder Contents/MacOS inside the bundle. In this same folder i also store:

firebird.conf
firebird (a folder)

..firebird.msg

..libfbembed.dylib
..libicudata.dylib
..libicui18n.dylib
..libicuuc.dylib
..security2.fdb
..bin (a folder)
....fb_lock_mgr..
....gbak
....isql
....firebird (a folder)
......firebird.msg
..intl (a folder)
....fbintl.conf
....fbintl.dylib

I used dots instead of spaces to indent because the blog editor keeps ignoring leading spaces.
Indented files are stored it the folder above them so, for example, gbak is stored in the bundle as Contents/MacOS/firebird/bin/gbak.

I added some "copy files" build phase in XCode to copy the Firebird files from /Users/fulvio/firebird_runtime to the bundle.

Linking

I link the program against /Users/fulvio/firebird_runtime/libfbembed.dylib adding the following text to Other linker flags:

-L/Users/fulvio/firebird_runtime

and adding the Firebird .dylib files to the "Link Binary With Libraries" build phase in XCode.

Environment variables

The next step is to set the FIREBIRD environment variable to the executable folder.
The command line is "export FIREBIRD=."
The same result can be obtained adding

<key>LSEnvironment</key>
<dict>
<key>FIREBIRD</key>
<string>.</string>
</dict>

to the Info.plist file contained in the bundle.

firebird.conf

Open the "firebird.conf" file and look for #RootDirectory. The leading pound sign means that the line is commented. Change it to:

RootDirectory = ./firebird

note that the line is not commented any more.

Patching

The bundle still doesn't work because the program looks for the libraries in the original framework's location, not in the bundle.
.dylib files contain their original full path and the original full path of all the used libraries, and executable files do the same. You can verify this running the otool -L and otool -D commands.
I solved this problem using the install_name_tool command that changes the file reference contained in libraries and executables. It only works if the new paths are not longer than the old ones, but this is not a problem for us. I use a "Run Script" build phase in XCode to execute the following script:

#!/bin/bash
EXECFILE=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}
LIBPATH=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/firebird
LIBBINPATH=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/firebird/bin
# path of library files relative to the main executable
NEWLIBPATH="@executable_path/firebird"
# path of library files relative to other library files
NEWLIBPATH_FOR_LIBS="@loader_path"
# path of library files relative to executables in the "firebird/bin" folder
NEWLIBPATH_FROM_BIN="@loader_path/.."
OLDLIBPATH="/Library/Frameworks/Firebird.framework/Versions/A/Libraries"
OLDLIBFBEMBEDFILENAME="/Library/Frameworks/Firebird.framework/Versions/A/Firebird"

# change the references in the files contained in the "firebird" folder
for TARGET in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do
LIBFILE=${LIBPATH}/${TARGET}
OLDTARGETID=${OLDLIBPATH}/${TARGET}
NEWTARGETID=${NEWLIBPATH}/${TARGET}
NEWTARGETID_FOR_LIBS=${NEWLIBPATH_FOR_LIBS}/${TARGET}
install_name_tool -id ${NEWTARGETID_FOR_LIBS} ${LIBFILE}
install_name_tool -change ${OLDTARGETID} ${NEWTARGETID} ${EXECFILE}
for POSSIBLECALLERNAME in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do
POSSIBLECALLERFILE=${LIBPATH}/${POSSIBLECALLERNAME}
install_name_tool -change ${OLDTARGETID} ${NEWTARGETID_FOR_LIBS} ${POSSIBLECALLERFILE}
done
done

# change the references in the files contained in the "firebird/bin" folder
for TARGET in gbak isql ; do
FILE=${LIBBINPATH}/${TARGET}
for POSSIBLECALLEDNAME in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do
OLDTARGETID=${OLDLIBPATH}/${POSSIBLECALLEDNAME}
NEWTARGETID=${NEWLIBPATH_FROM_BIN}/${POSSIBLECALLEDNAME}
install_name_tool -change ${OLDTARGETID} ${NEWTARGETID} ${FILE}
done
# change the reference to libfbembed into the program, that contains a reference to a different name (the framework name)
NEWTARGETID=${NEWLIBPATH_FROM_BIN}/libfbembed.dylib
install_name_tool -change ${OLDLIBFBEMBEDFILENAME} ${NEWTARGETID} ${FILE}
done

# change the reference to libfbembed into the caller program, that contains a reference to a different name (the framework name)
NEWTARGETID=${NEWLIBPATH}/libfbembed.dylib
install_name_tool -change ${OLDLIBFBEMBEDFILENAME} ${NEWTARGETID} ${EXECFILE}

I learned something about .dlybs patching from

http://ynniv.com/blog/2006/02/deploying-app-that-use-dylibs-on-mac.html

but this link does not seem to work any more.

1 comment:

  1. Thank you! It's usefull, and I had read something about dlybs on Apple site and it's right about. I do the same to Qt applications.
    Now, some one can create a guide to do the same on Lazarus IDE.

    ReplyDelete