Using CMake to create a bundle for a Qt4/VTK/CGAL project

I write a GUI application that uses Qt4 as a frontend as well as other libraries in the backend, like VTK, CGAL, Boost, and what-have-you, and because I use CMake as my build system, it’s quite manageable for developers on either Windows, OS X, or Linux to build from source code. It gets a little more complicated when I need to build distributable executables for non-developers to use, and so far I do not have a good way to do this written into my main CMakeLists.txt. However, I was recently able to build a bundle, which is OS X’s distributable executable that you can pass around, put into your Applications folder, and double-click to run, and this blog has some details that might be useful, especially if you have experience building distributables on a Linux environment.

To begin, note the versions of things that I’m using:

  • OS X 10.8.5
  • CMake 2.8.12
  • CGAL 4.3
  • Qt 4.8.5
  • VTK 5.10.1

The general approach is the following:

  1. Build CMake
  2. Build the library dependencies using our CMake
  3. Build our project
  4. Install our project in a portable way – in Linux, the executable and its library dependencies are copied into an install folder. The executable is hardwired with a path (RPATH) that points to where to look for library dependencies. The executable is portable in the sense that the RPATH can be set to a path relative to the executable (with the @ORIGIN keyword), so you can relocate the executable and still have it find the libraries it needs.
    In OS X, the distributable executable is a bundle, which is basically a folder containing the executable and the libraries in a particular structure. CMake has a module to set this up automatically on install. This module also performs bundle fixup where library dependencies are pointed to the ones in the bundle folder rather than the ones on your system.

Why are we doing it this way?
This approach might seem like overkill: Homebrew is a package manager that lets you install CMake and all the libraries you probably would need. However, there is an issue with the last step in the general approach. Basically, when CMake copies the dependency libraries into the bundle, it messes up. The only way I’ve found how to get this working is not to use Homebrew’s CMake but use our own CMake.

Okay, let’s step through.

1. Build CMake
I’m just going to assume you got the source package, built and installed it, and added the bin folder to your path. One further thing that I did was edit a file that was giving me errors during bundle fixup. In share/cmake-2.8/Modules/BundleUtilities.cmake:

--- BundleUtilities.orig.cmake 2013-11-28 01:11:34.000000000 -0800
+++ BundleUtilities.cmake 2013-11-02 20:52:04.000000000 -0700
@@ -585,9 +585,9 @@
 endif()
 endforeach()

- if(BU_CHMOD_BUNDLE_ITEMS)
+ #if(BU_CHMOD_BUNDLE_ITEMS)
 execute_process(COMMAND chmod u+w "${resolved_embedded_item}")
- endif()
+ #endif()

 # Change this item's id and all of its references in one call
 # to install_name_tool:

2. Build the library dependencies using our CMake
I won’t write anything about this except post the line that I used to configure Qt4. Basically, I lifted the configuration from the brew script for qt4, which seemed to have some OS X specific things in it:

./configure -system-zlib -confirm-license -opensource -nomake demos -nomake     examples -cocoa -fast -release -no-3dnow -no-ssse3 -platform unsupported/macx-  clang -nomake docs -qt3support -prefix ${PREFIX}

3. Build our project
I also won’t write anything about this part since it is just a matter of using find_package to locate and pull in the required libraries, and I assume you’ve got your project configured.

4. Install our project in a portable way
So here we throw in a few extra lines into CMakeLists.txt. A full example is available from the CMake wiki here. Say your build target is named MyProgram, then you would include some install instructions that look like this:

install( TARGETS
MyProgram
BUNDLE DESTINATION .
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
)

Now I already had an install directive, but line 3 was the new line to add. Basically, if you’re building on any other OS other than OS X, your executable will end up in ${CMAKE_INSTALL_PREFIX}/bin, otherwise, it will go into a bundle folder named after the build target, which in this case will be ${CMAKE_INSTALL_PREFIX}/./MyProgram.app. This explains the following lines that come afterwards:

set( APPS "\${CMAKE_INSTALL_PREFIX}/MyProgram.app" )
set( DIRS "\${CMAKE_INSTALL_PREFIX}/lib" )
set(qtconf_dest_dir MyProgram.app/Contents/Resources)
install(CODE "
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"\")
" COMPONENT Runtime)
install( CODE "
include(BundleUtilities)
fixup_bundle( \"${APPS}\" \"\" \"${DIRS}\" )
" Component Runtime )

Lines 1-3 just define some helper variables that get used in the following lines. ${APPS} points to the bundle folder and ${DIRS} points to where to look for libraries it depends on. I set this to point to where I install my project library build targets.

Lines 4-6 generates a file in the bundle folder that is necessary to get Qt applications working properly.

Lines 7-11 performs the bundle fixup: when the bundle is initially installed, the executable is linked to required libraries via paths specific to your system. These include libraries external to your project and library build targets within your project. fixup_bundle will identify the libraries from the bundle pointed to by ${APPS}, copy them into the bundle folder, then relink the executable to these copies instead of the ones installed on your system.

It’s a little weird how BundleUtilities actually finds out the library dependencies. On the one hand, it is supposed to look in ${DIRS} to find libraries, but it might not be able to, and for my project, I had to manually copy the ones it missed into the bundle folder and rerun the cmake install to get it fixed up. Also, although I didn’t specify VTK and the others in ${DIRS}, it was able to find and copy these in. In short, during bundle fixup, some errors may occur that you should pay attention to, and if you see missing libraries that should have been copied, you can probably get around by manually copying them into the bundle right next to the executable and rerunning the install.

Additional notes

  1. In Linux, you could inspect dynamic library dependencies with ldd. In OS X, you can make use of otool -L to do the same thing.
  2. In OS X, you can do some manual fixup of library dependencies as BundleUtilities does by using install_name_tool.

Hopefully, reporting all of this in one place was helpful to someone. CMake really works wonders, but the only thing is hunting down enough information to be able to do what you want can take more time than expected. Also, this is my first serious take on building projects on OS X so there was a bit of a learning curve coming from Linux, but it’s not so bad now.

Feel free to leave questions in the comments.

Happy thanksgiving!

Advertisements