Using CPack with BundleUtils
1 minute read.
It’s been way too long since my last blog post, I know. But great things have
been happening, which led me to cpack
and other stuff. I came across a specific
problem that I seem to be the first to spend days of work time on, so I really hope
save someone out there the time.
If you’re deep into the issue and here for the solution skip the next two sections.
CPack
I spent quite a while trying to figure out how to create nice installers for a product that I’m building for my company, Vhite Rabbit. I’ll tell you way more about that once our initial MVP launched.
Since our product has a native application component, built with C++ and CMake, CPack eventually showed up as the best solution.
CPack allows you to generate and build installer and package build files, pretty
similar to CMake. You use a generator, like NSIS
(Null Soft Installer) or
DEB
(Debian Package) etc, to then end up with a Windows installer or Debian
package. Very simple to use and you don’t have to learn a new installer system
or package specification semantics for every system.
Underlying, cpack
uses the install
target from CMake, of which the result
is bundled. This is great, because you can “simulate” and installed version of
your project by installing it into some directory.
The first problem you’ll encounter on Windows is missing DLLs in case you are
working with any external libraries. Or missing .so
files. Or missing .dylib
files.
That’s what the BundleUtils module is for in cmake.
BundleUtils
Provides the fixup_bundle
function. It is there to resolve dependent .dll/.so/.dylib
files.
It is special as it should be executed during “install time”.
Picture this: “configuration time” is when you run cmake on your cmake project. “generation time” is while
cmake generates your build files. “compile time” is during execution of your generated build files and finally
“install time” could be considered during execution of the cmake_install.cmake
that CMake generates
for the install
target. And finally, consider the execution of the package
target (which calls cpack
to be “package time”).
fixup_bundle
belongs into that exact cmake_install.cmake
file. But how? The official documentation
recommends the folling:
set(APPS ${CMAKE_INSTALL_PREFIX}/MyApplication.exe) install(CODE " include(InstallRequiredSystemLibraries) include(BundleUtilities) fixup_bundle(\"${APPS}\" \"\" \"\")" COMPONENT Runtime)
The CODE
keyword here will mark the cmake code you pass for direct insertion into the cmake_install.cmake
.
BundleUtils and CPack
There is an issue with this, though: ${CMAKE_INSTALL_PREFIX}
will evaluate during configure time, not during
“package time”. CPack needs to pass a new CMAKE_INSTALL_PREFIX
to cmake, though, to be able to install your
project into a temporary directory that will be packaged.
This, therefore, cannot be the final form.
Another step, that you will find on the internet is to escape the variable so that it evaulates at package time.
Use \${CMAKE_INSTALL_PREFIX}
(the \\
is the difference) instead. But this didn’t work for me, as CPack specified the install prefix relative
to a destination directory, which, though, is available through the environment.
So here is the final form:
set(APPS \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/MyApplication${CMAKE_EXECUTABLE_SUFFIX}) install(CODE " include(BundleUtilities) fixup_bundle(\"${APPS}\" \"\" \"\")" COMPONENT Runtime)
In case this saved you hours of work, you may want to consider buying me coffee. I would appreciate it :)