CMake Fundamentals Part 9

by | May 10, 2021 | CMake | 0 comments

In many scenarios the installation-support we’ve developed over the previous two parts may be sufficient, however, in some situations the ultimate end-goal might be to provide a self-contained installer package, that could be delivered to the users of your library or application. Of course, CMake can help with that as well. The support for packaging comes in the form of yet another tool in the CMake family – next to cmake itself and ctestcpack.
Note that this is not a complete tutorial, not even a comprehensive overview, but rather something of a gentle introduction to get you started if you do find the need for it.

CPack – Why?

Packaging a project for installation could be as simple as putting the desired artifacts into an archive like .tar.gz or a .zip file. This could be handled with some additional simple scripting that relies on the already developed install targets. On the other hand we may want to go a step beyond that and provide a platform-specific installer package, like a .deb or an .rpm, or maybe even a GUI-based installer. These approached would require much more work as well as tool and platform-specific knowledge. Luckily CPack can abstract most of the complexities away for us.

CPack is able to generate installers for a wide range of platforms, using multiple generators, including all of the ones mentioned above. It relies on the install targets we already developed, and for the most part, comes down to configuring the scripting that CMake will generate for us. This can be as simple, or as complex, as we’d like (or need) – depending on what we expect the final installation process to look like.

CPack – How?

The most basic use of CPack involves including the CPack module – this is what will generate the packaging scripts – and defining some variables prior to that – this configures the CPack module, telling it exactly what our expectations are. Let’s see this in practice.

First, we will encapsulate packaging into a dedicated subdirectory – packaging. For now it will contain only a single CMakeLists.txt file, but it also might contain some additional assets we might wish to package with our installer. The packaging subdirectory should be added at the end of the top-level CMakeLists.txt, most importantly after all the install commands have already been processed. We end up with the following setup:

.
├── CMakeLists.txt
├── packaging
│   └── CMakeLists.txt

CMakeLists.txt:

add_subdirectory(packaging)

All of the heavy lifting is done already – we’ve defined how our library and all its components should be installed – this is handled by the install target. CPack fully relies on this installation process. All that we need to do now is to provide sufficient information to CPack. This is done in the packaging/CMakeLists.txt file:

set(CPACK_PACKAGE_NAME                  Maths)
set(CPACK_PACKAGE_VENDOR                CMakeFundamentalsCo)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY   "CMake Fundamentals super-amazing mathematics library.")
set(CPACK_PACKAGE_INSTALL_DIRECTORY     ${CPACK_PACKAGE_NAME})
set(CPACK_PACKAGE_VERSION_MAJOR         ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR         ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH         ${PROJECT_VERSION_PATCH})
set(CPACK_VERBATIM_VARIABLES            TRUE)

if (WIN32)
    set(CPACK_GENERATOR     ZIP)
else()
    set(CPACK_GENERATOR     TGZ)
endif()

include(CPack)

And for the simplest possible case that’s it. Most of these variables should be self explenatory, but here’s what they’re responsible for anyway:

Variable Purpose
CPACK_PACKAGE_NAME The name of the package.
CPACK_PACKAGE_VENDOR The entity responsible for the package.
CPACK_PACKAGE_DESCRIPTION_SUMMARY Short description of what the package is / what it contains.
CPACK_PACKAGE_INSTALL_DIRECTORY The additional (sub)directory to install/unpack the package into (rather than extracting the contents directly).
CPACK_PACKAGE_VERSION_ The version of the package. This will become part of the package name.
CPACK_VERBATIM_VARIABLES Ensure correct escape-sequences of all variables. Always set this to True.
CPACK_GENERATOR Default generator(s) to use if not specified explicitly on the commandline.

Let’s see how to generate the package.

$ cmake -DBUILD_SHARED_LIBS:BOOL=ON -B build -S .
$ cmake --build build -j8
$ cd build
$ cpack
CPack: Create package using TGZ
CPack: Install projects
CPack: - Install project: Fundamentals []
CPack: Create package
CPack: - package: /home/user/cmake_fundamentals/build/Maths-0.1.0-Linux.tar.gz generated.

The generated Maths-0.1.0-Linux.tar.gz archive contains exactly the same files we’d expect from invoking the install target. Not very surprising or exciting is it? None the less, that’s a very reasonable and common way to distribute a specific version of a library in binary form via github. CPack has much more to offer though. One interesting option for linux systems are self-extracting archives:

if (WIN32)
    set(CPACK_GENERATOR     ZIP)
else()
    set(CPACK_GENERATOR     TGZ STGZ)
endif()

Executing cpack now will generate both the .tar.gz archive and a self-extracting .sh package.

# in the build directory
$ cmake .
$ cpack
CPack: Create package using STGZ
(...)
CPack: - package: /home/user/cmake_fundamentals/build/Maths-0.1.0-Linux.sh generated.

The generated Maths-0.1.0-Linux.sh is a self-contained installer that’s rather convenient and easy to use:

$ ./Maths-0.1.0-Linux.sh --help
Usage: ./Maths-0.1.0-Linux.sh [options]
Options: [defaults in brackets after descriptions]
  --help            print this message
  --version         print cmake installer version
  --prefix=dir      directory in which to install
  --include-subdir  include the Maths-0.1.0-Linux subdirectory
  --exclude-subdir  exclude the Maths-0.1.0-Linux subdirectory
  --skip-license    accept license

$ ./Maths-0.1.0-Linux.sh --version
Maths Installer Version: 0.1.0, Copyright (c) CMakeFundamentalsCo

$ ./Maths-0.1.0-Linux.sh --prefix=. --include-subdir
Maths Installer Version: 0.1.0, Copyright (c) CMakeFundamentalsCo
This is a self-extracting archive.
The archive will be extracted to: .

Using target directory: ./Maths-0.1.0-Linux
Extracting, please wait...

Unpacking finished successfully

This will have the same result as extracting the .tar.gz, except we now have a nice interface.

Packaging source files

A common practice is to also provide packaged source files for every release of a library. This can easily be achieved with CPack as well, with no additional work whatsoever. All it takes is the following call to cpack:

$ cpack -G TGZ --config CPackSourceConfig.cmake

This will package the entire source tree using the specified generator (here a .tar.gz archive will be created). Careful though! For in-source build this will package the build directory as well, so make sure to use an out-of-source build.

Platform-specific packages

CPack supports popular Linux packaging formats – DEB and RPM, amongst others. These are essentally just simple archives with some metadata required by the system’s package manager. Creating a DEB package can be as simple as specifying this generator in the CPACK_GENERATOR variable, and satisfying one additional requirement – a maintainer of the package must be set:

if (WIN32)
    set(CPACK_GENERATOR     ZIP)
else()
    set(CPACK_DEBIAN_PACKAGE_MAINTAINER cmake_fundamentals@blog.com)
    set(CPACK_GENERATOR     TGZ STGZ DEB)
endif()

CPack will now generate a .deb package for us:

$ cpack
CPack: Create package using DEB
CPack: Install projects
CPack: - Install project: Fundamentals []
CPack: Create package
-- CPACK_DEBIAN_PACKAGE_DEPENDS not set, the package will have no dependencies.
CPack: - package: /home/user/cmake_fundamentals/build/Maths-0.1.0-Linux.deb generated.

Note that we’re informed that dependencies for this package are not configured. I’ll get back to this in a moment.

The package can be installed using dpkg, apt, or whatever other package manager you choose, that supports .deb packages.

$ sudo dpkg --install --instdir=$(pwd) Maths-0.1.0-Linux.deb
(Reading database ... 330468 files and directories currently installed.)
Preparing to unpack Maths-0.1.0-Linux.deb ...
Unpacking maths (0.1.0) over (0.1.0) ...
Setting up maths (0.1.0) ...

This works, kinda. However, it’s probably not a good idea to distribute a DEB or RPM package with this kind of minimal configuration. We were warned that no dependencies for this package were configured – if there were any it would be a good idea to actually do so. Another common practice for such distributions is to provide component-based packages – separate packages for runtime components – shared libs and executables, development components – static libraries, headers, documentation, source code, etc. It’s possible to define all this using CPack, however, I’ve deliberately chosen to omit this here. If you do need to distribute your project in this way I’d encourage you to check both the CMake/CPack documentation and the documentation for your chosen packaging format.

GUI-based installers

Last, but na least, CPack also supports generating GUI-based installers. This includes the cross-platform Qt Installer Framework, WIX and NSIS on Windows, and probably many more, including some MacOs options that I’m not aware of.

Much like before, it’s possible to generate a GUI-based installer without any additional effort, by just specifying the chosen generator. As was the case with DEB and RPM packages, if your project has reached the level where a GUI-based installer seems like a worthwhile option it might make sense to put in some more effort and divide the package into components, add an appropriate description, etc. Yet again, this is where I leave you with the documentation.

Let’s just see an example of the installer that WIX generates for us on Windows:

if (WIN32)
    set(CPACK_GENERATOR     ZIP WIX)
else()
(...)
It looks more or less as one would expect an installer to look like on Windows. Also note that all the icons and images in the installers window are customizable with CPack. If you intend to try this out for yourself you will need to install the WIX toolset.

Summary

If you start releasing your project on a regular basis it might be a good idea to provide convenient packages for your users. Simple archives of both binaries and source code are a common choice for distributing open-source projects on github. CPack also gives you an option to generate platform-specific packages recognized by package managers or even GUI-based installers. Those, however, require quite a bit more work to do correctly.

 

References

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

Share This