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 hap­pen­ing, which led me to cpack and oth­er stuff. I came across a spe­cif­ic prob­lem that I seem to be the first to spend days of work time on, so I re­al­ly hope save some­one out there the time.

If you’re deep in­to the is­sue and here for the so­lu­tion skip the next two sec­tions.

CPack

I spent quite a while try­ing to fig­ure out how to cre­ate nice in­stall­ers for a prod­uct that I’m build­ing for my com­pa­ny, Vhite Rab­bit. I’ll tell you way more about that once our ini­tial MVP launched.

Since our prod­uct has a na­tive ap­pli­ca­tion com­po­nent, built with C++ and CMake, CPack even­tu­al­ly showed up as the best so­lu­tion.

CPack al­lows you to gen­er­ate and build in­stall­er and pack­age build files, pret­ty sim­i­lar to CMake. You use a gen­er­a­tor, like NSIS (Null Soft In­stall­er) or DEB (De­bian Pack­age) etc, to then end up with a Win­dows in­stall­er or De­bian pack­age. Very sim­ple to use and you don’t have to learn a new in­stall­er sys­tem or pack­age spec­i­fi­ca­tion se­man­tics for ev­ery sys­tem.

Un­der­ly­ing, cpack us­es the install tar­get from CMake, of which the re­sult is bun­dled. This is great, be­cause you can “sim­u­late” and in­stalled ver­sion of your project by in­stalling it in­to some di­rec­to­ry.

The first prob­lem you’ll en­counter on Win­dows is miss­ing DLLs in case you are work­ing with any ex­ter­nal li­braries. Or miss­ing .so files. Or miss­ing .dylib files.

That’s what the BundleU­ti­ls mod­ule is for in cmake.

BundleUtils

Pro­vides the fixup_bundle func­tion. It is there to re­solve de­pen­dent .dll/.so/.dylib files. It is spe­cial as it should be ex­e­cut­ed dur­ing “in­stall time”.

Pic­ture this: “con­fig­u­ra­tion time” is when you run cmake on your cmake project. “gen­er­a­tion time” is while cmake gen­er­ates your build files. “com­pile time” is dur­ing ex­e­cu­tion of your gen­er­at­ed build files and fi­nal­ly “in­stall time” could be con­sid­ered dur­ing ex­e­cu­tion of the cmake_install.cmake that CMake gen­er­ates for the install tar­get. And fi­nal­ly, con­sid­er the ex­e­cu­tion of the package tar­get (which calls cpack to be “pack­age time”).

fixup_bundle be­longs in­to that ex­act cmake_install.cmake file. But how? The of­fi­cial doc­u­men­ta­tion rec­om­mends the folling:

set(APPS ${CMAKE_INSTALL_PREFIX}/MyApplication.exe)
install(CODE "
        include(InstallRequiredSystemLibraries)
        include(BundleUtilities)
        fixup_bundle(\"${APPS}\" \"\" \"\")"
    COMPONENT Runtime)

The CODE key­word here will mark the cmake code you pass for di­rect in­ser­tion in­to the cmake_install.cmake.

BundleUtils and CPack

There is an is­sue with this, though: ${CMAKE_INSTALL_PREFIX} will eval­u­ate dur­ing con­fig­ure time, not dur­ing “pack­age time”. CPack needs to pass a new CMAKE_INSTALL_PREFIX to cmake, though, to be able to in­stall your project in­to a tem­po­rary di­rec­to­ry that will be pack­aged.

This, there­fore, can­not be the fi­nal form.

An­oth­er step, that you will find on the in­ter­net is to es­cape the vari­able so that it evaulates at pack­age time. Use \${CMAKE_INSTALL_PREFIX} (the \\ is the dif­fer­ence) in­stead. But this didn’t work for me, as CPack spec­i­fied the in­stall pre­fix rel­a­tive to a des­ti­na­tion di­rec­to­ry, which, though, is avail­able through the en­vi­ron­ment.

So here is the fi­nal 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 con­sid­er buy­ing me cof­fee. I would ap­pre­ci­ate it :)