Area Man (Kinda) Figures out How to Use CPack

Lately I have been working on a C++11 project that uses CMake. I find CMake to be approachable, useful, and sometimes somewhat weird. But it gets the job done.

I suppose I should document what I have managed to learn so far (which is: just enough to be useful), and I will do that in due time, in a regularly scheduled future weblog update (haha).

For now here are some notes about a thing I struggled with, for longer than I should have: making an RPM package from ye olde CMake project.

CMake ships with a companion program, CPack, which makes creating binary packages easy. All you need is a line like this in your top-level CMakeLists.txt:

include(CPack)

Our project generates the good ole Unix Makefiles, so I should have been able to do just run some commands and get on with my day:

$ cd $BUILDDIR
$ cmake $SOURCEDIR
$ make package # or just run 'cpack'

As we know nothing in computering land works that way.

With the basic configuration, the above produced three packages: a self-extracting shell script, a tar.gz package, and a Unix compressed .Z package. But here’s a problem:

$ ls -l project-0.1.1-Linux.*
-rwxrwxrwx 1 sajith sajith 3818 Sep  8 10:35 project-0.1.1-Linux.sh
-rw-rw-r-- 1 sajith sajith   29 Sep  8 10:35 project-0.1.1-Linux.tar.gz
-rw-rw-r-- 1 sajith sajith   54 Sep  8 10:35 project-0.1.1-Linux.tar.Z

They are all empty! Also I still do not have that RPM package I needed. So I set the CPACK_GENERATOR variable before the include(CPack) line:

set(CPACK_GENERATOR "RPM")
include(CPack)

In order to have the intended artifacts (binaries, documentation, etc) inside the archives, I also needed some install lines in the appropriate CMakeLists.txt files. (We have not been “make install”-ing our project, so we had omitted the install lines. CMake-built binary packages get their contents from the install step, and it took a bit of figuring out.)

install(TARGETS binary DESTINATION bin)
install(DIRECTORY project-config-files DESTINATION etc)
install(FILES README.txt DESTINATION share/doc/project)

With that, I have a basic RPM file. For further customization, there are a bunch of extra variables that we can set:

set(CPACK_GENERATOR        "RPM")
set(CPACK_PACKAGE_VERSION  ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_RELEASE  1)

set(CPACK_PACKAGE_NAME                 "usual-widgets")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY  "Some usual widgets for day-to-day use")
set(CPACK_PACKAGE_VENDOR               "Usual Business Corp")
set(CPACK_PACKAGE_CONTACT              "Your Humble Packager <packager@example.net>")

set(CPACK_PACKAGING_INSTALL_PREFIX  ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_FILE_NAME         "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}")

set(CPACK_RPM_PACKAGE_LICENSE  "GPLv3")
set(CPACK_RPM_PACKAGE_GROUP    "Applications/Internet")
set(CPACK_RPM_PACKAGE_URL      "https://project.example.net/packages")

We can also instruct CPack to generate additional kinds of packages:

set(CPACK_GENERATOR  "RPM;DEB;TGZ")

And there are more knobs to turn:

set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
set(CPACK_DEBIAN_PACKAGE_VERSION      1)
set(CPACK_DEBIAN_PACKAGE_SECTION      "Network")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE     "https://project.example.net/packages")

Another thing I figured out is that I can set CMAKE_INSTALL_PREFIX to somewhere other than the default /usr/, when running cmake:

$ cmake -DCMAKE_INSTALL_PREFIX=/opt/UsualBusinessCorp/ $SOURCEDIR

I still do not know how to sign the packages, etc., but then again, this is just enough to be useful, and that is all I need.

Extra: verbose output

What if I need a little more verbose output from CPack?

cpack -V -G RPM -D CPACK_RPM_PACKAGE_DEBUG=1
cpack -V -G DEB -D CPACK_DEBIAN_PACKAGE_DEBUG=1

Extra: the dreaded comma

Here’s a frustrating but really silly thing I dealt with: I inserted an extra comma in one of the set() directives by mistake, and then spent an inordinate amount of time figuring out why things weren’t working the way I thought they should be working.

CMake silently ignores the directive when you do set(VAR, VAL): it has to be set(VAR VAL) or CMake will do its thing and not the thing you’re asking it to do. You do not want this to happen.

(Posted on September 8, 2017.)