skip to main content

LCFG Build Tools : CMake Recipes

The intention of this page is to document bits of common CMake usage in LCFG packages. It is not a complete manual for CMake, for that you should go to the CMake Online Documentation. If you have any good examples or think something is missing please let us know by sending an email to the LCFG team.

Basic CMake Usage

Every CMake build file (CMakeLists.txt) needs to begin with a project line which gives the name of the project. It should also include a line stating the minimum supported version of cmake. If you want to you can support earlier versions, such as 2.2 or 2.4. This will alter the behaviour of some commands and provide backwards compatible names for others. For LCFG projects you should set this to 2.6 (the latest stable release branch of CMake), the LCFG CMake macros are not guaranteed to work with any earlier version.

project(foo)

cmake_minimum_required(VERSION 2.6)

Extra Substitution Variables

Once you have specified a CMake variable it can be used as a substitution variable (e.g. @FOO@). This is a lengthy example taken from the LCFG client component. They can end up creating maintenance problems, and make porting to new platforms more difficult, particularly if you are setting them differently for different target platforms. If you are using huge numbers of variables just for this purpose you might want to question whether you are going about it the right way and whether the values could be detected at runtime or, in the case of an LCFG component, actually be specified as LCFG resources

# Local variables

set(BOOTSTAMP /var/run/bootstamp)
set(CONFDIR "${LCFGCONF}/profile")
set(CALLDIR "${CONFDIR}/callbacks")
set(CTXDIR "${CONFDIR}/context")
set(DBMDIR "${CONFDIR}/dbm")
set(LOCALDIR "${CONFDIR}/local")
set(PROFDIR "${CONFDIR}/xml")
set(PWFILE "${CONFDIR}/.passwd")
set(RELEASEFILE /etc/LCFG-RELEASE)
set(RESTARTING "${LCFGTMP}/${COMP}.restarting")
set(RPMCDIR "${CONFDIR}/rpmcfg")
set(TMPDIR "${LCFGTMP}/client")

set(RDXPROF "${LCFGSBIN}/rdxprof")
set(SETCTX "${LCFGSBIN}/setctx")

Logging to the Screen

Often when debugging a build process you want to send messages to the screen to display the contents of variables or to show what route through the code is being taken. CMake provides a useful message command.

message("hello world")
message(STATUS "Package name is ${LCFG_FULLNAME}")
message(SEND_ERROR "Should not have got here")
message(FATAL_ERROR "Something bad happened")

See the CMake manual for a full description of the different status levels which can be used.

Locating Required Programs

It is possible to detect the location of required programs at build time. This can be a good idea as it allows the downstream user to decide where they want to install various packages (e.g. into /usr/local/bin). Note that if you are intending your packages to be built in a chroot (using mock on Fedora, for instance) you must ensure that the build-dependencies are correctly specified or the programs will not be found. This is an example taken from the LCFG client component.

# Various programs provided by lcfg-utils

find_program(QXPROF qxprof)
find_program(SXPROF sxprof)

The locations of the sxprof and qxprof programs will then be stored in the SXPROF and QXPROF CMake variables. They will also be available for use as the substitution variables @SXPROF@ and @QXPROF@.

Note that if the specified program is not found CMake will not automatically fail. If you require the existence of the command then you need to follow-up with a test and then use the CMake MESSAGE() command. For example:

find_program(QXPROF qxprof)
if(NOT QXPROF)
  message(FATAL_ERROR "Could not find qxprof command")
endif(NOT QXPROF)

Creating Empty Directories

Sometimes it is necessary to ship empty directories in a packages, to hold log files, temporary files, etc.. This is an example taken from the LCFG client component.

INSTALL(DIRECTORY DESTINATION ${CALLDIR})
INSTALL(DIRECTORY DESTINATION ${CTXDIR})
INSTALL(DIRECTORY DESTINATION ${DBMDIR})
INSTALL(DIRECTORY DESTINATION ${RPMCDIR})
INSTALL(DIRECTORY DESTINATION ${PROFDIR})

INSTALL(DIRECTORY DESTINATION ${LCFGLOCK})
INSTALL(DIRECTORY DESTINATION ${LCFGLOG})
INSTALL(DIRECTORY DESTINATION ${LCFGSTATUS})
INSTALL(DIRECTORY DESTINATION ${LCFGTMP})

Installing an Extra File

It is sometimes necessary to distribute extra files which do not fit into the standard LCFG component layout. This is an example taken from the LCFG defetc-sl5 package which installs two extra files.

INSTALL(FILES passwd group DESTINATION "${LCFGDATA}/common")

You would also use this to install header files, for instance:

install(FILES pkgtools.h DESTINATION include/lcfg)

Note that if, like the example above, you do not give an absolute path it will be relative to the CMAKE_INSTALL_PREFIX directory. By default that is /usr/local but for building RPMs that is overridden to be /usr.

Installing a Set of Files

You can install the entire contents of a directory into another or more reasonably you might want to just copy those files which match a pattern. You can use multiple patterns and can also exclude some files (the CVS directory, for instance) based on patterns. This is an example taken from the LCFG logserver component.

# Find all gif images and install them into the component icon directory

INSTALL(DIRECTORY icons/
        DESTINATION ${ICONDIR}
        FILES_MATCHING PATTERN "*.gif")

It is worth noting the trailing-slash on the "icons" directory name, this is important. Without that trailing-slash you would end up installing the directory itself within the target directory, not just the matching files.

This can have problems, mostly related to the way LCFG components use templated files (i.e. anything matching *.cin. There is a simple way around this issue, for example in the lcfg-cron component:

install(DIRECTORY crontabs/
        DESTINATION ${CRONTABDIR}
        FILES_MATCHING
        PATTERN "*"
        PATTERN "*.cin" EXCLUDE)

That will install all the default crontab files in that directory except those matching the excluded pattern. You can have multiple patterns and multiple excluded patterns to achieve what you want.

Installing Executables

The only difference between this and the earlier file-installation example is that CMake will take care of ensuring the installed file has the correct executable permissions. This is an example taken from the LCFG boot component.

INSTALL(PROGRAMS waitonboot DESTINATION ${LCFGLIB})

Compiling Executables

This is a fictional example to reduce the problem to its simplest form. You only need to specify the list of "*.c" files needed to make the executable. CMake will scan those files and work out which header files to consider as dependencies. In this case the compiled executable is installed into a bin directory which is relative to the directory stored in the CMake cache variable CMAKE_INSTALL_PREFIX. On Unix the default for that path is /usr/local but we override it to be /usr to stay in line with RPM packaging practices.

add_executable(foo foo.c)
install(TARGETS foo DESTINATION bin)

Linking with an External Library

Linking an executable against an external library is fairly trivial to do with CMake. The location of the library should be discovered at build time. The reference to PCRE_LIBRARY is the CMake variable into which the location is stored and then accessed.

add_executable(foo foo.c)
find_library(PCRE_LIBRARY pcre)
target_link_libraries(foo ${PCRE_LIBRARY})
install(TARGETS foo DESTINATION bin)

The discovery of the locations for some libraries is supported by CMake. Where this is the case it is highly recommended that you utilise that support. This will help achieve a greater degree of platform independence. There is some information on the CMake wiki, it is a little out-of-date but most of it is still useful and relevant.

The simplest way to check whether CMake supports discovering the locations of headers and libraries for a particular set of software is to look at the files named like Find*.cmake in the /usr/share/cmake/Modules/ directory. Typically each module is well documented at the top of the file with a list of which CMake variables will be set.

Taking the lcfg-nsu component, which uses some X11 libraries, as an example:

  find_package(X11)

  if(NOT X11_FOUND)
    message(FATAL_ERROR "Failed to find X11 which is required to build nsu")
  endif(NOT X11_FOUND)

  set(LIBS ${LIBS} ${X11_LIBRARIES})

  target_link_libraries(nsu ${LIBS})

You might also need to include additional headers. If you have used a distributed CMake "Find" module then you should already have a variable with any extra paths necessary. FindZLIB, for example, creates a ZLIB_INCLUDE_DIR variable. To add the extra headers you just need:

include_directories(${ZLIB_INCLUDE_DIR})

If you need to find the path yourself then you can do it like this example for the rpm library headers:

find_path(RPM_INCLUDE_DIR rpm/rpmlib.h)
if(NOT RPM_INCLUDE_DIR)
  message(FATAL ERROR "Could not find rpm header files")
endif(NOT RPM_INCLUDE_DIR)
include_directories(${RPM_INCLUDE_DIR})

Compiling a Shared Library

This example is taken from the LCFG pkgtools package with slight modifications. Again, only the "*.c" files need to be specified. Here there is an additional CMake cache variable which makes it work correctly with the macro in /etc/rpm/macros.cmake. This allows the install path to be modified so that it is correct on x86_64 as well as i386. The default value is lib which is the i386 path (and also correct for Debian x86_64 systems).

add_library(lcfg_pkgtools SHARED pkgtools.c)
set_target_properties(lcfg_pkgtools PROPERTIES VERSION 1.0.1 SOVERSION 1)
SET(CMAKE_INSTALL_LIBDIR lib CACHE PATH "Output directory for libraries")
install(TARGETS lcfg_pkgtools DESTINATION ${CMAKE_INSTALL_LIBDIR})

This results in these files being generated:

/usr/lib/liblcfg_pkgtools.so -> liblcfg_pkgtools.so.1
/usr/lib/liblcfg_pkgtools.so.1 -> liblcfg_pkgtools.so.1.0.1
/usr/lib/liblcfg_pkgtools.so.1.0.1

Compiling a Static Library

This example is also based on the LCFG pkgtools package and is fairly similar, if simpler, than the previous example. This will create the file /usr/lib/liblcfg_pkgtools.a

add_library(lcfg_pkgtools-static STATIC pkgtools.c)
SET(CMAKE_INSTALL_LIBDIR lib CACHE PATH "Output directory for libraries")
install(TARGETS lcfg_pkgtools DESTINATION ${CMAKE_INSTALL_LIBDIR})

Compiling a Library as Shared and Static

Typically it is necessary to generate both shared and static libraries. There is a slight complication here in that CMake will not let you have two targets with the same name and by default that is how the output file name is specified. It is easy enough to alter the output name but there is another bit of magic which needs to be added to avoid trouble. This example is based on the LCFG pkgtools package and you can see that it is also easy to link compiled libraries against external libraries (pcre in this case).

# Generate the pkgtools shared library.

add_library(lcfg_pkgtools SHARED pkgtools.c)
set_target_properties(lcfg_pkgtools PROPERTIES VERSION 1.0.1 SOVERSION 1)

# Cannot have two targets with the same name so the static version has
# '-static' appended and then the name of the output file is set
# separately.

add_library(lcfg_pkgtools-static STATIC pkgtools.c)
set_target_properties(lcfg_pkgtools-static PROPERTIES OUTPUT_NAME "lcfg_pkgtools")

# These next two lines are required but it is unclear exactly what they do.
# The CMake FAQ mentions they are necessary and it does not work otherwise.

SET_TARGET_PROPERTIES(lcfg_pkgtools PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(lcfg_pkgtools-static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

find_library(PCRE_LIBRARY pcre)

target_link_libraries(lcfg_pkgtools ${PCRE_LIBRARY})

SET(CMAKE_INSTALL_LIBDIR lib CACHE PATH "Output directory for libraries")

install(TARGETS lcfg_pkgtools lcfg_pkgtools-static
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})

Linking an Executable to a Compiled Library

Continuing with the previous example, it is equally easy to link an executable against a compiled library. Just use the target names:

add_executable(parse_pkgspec parse_pkgspec.c)
target_link_libraries(parse_pkgspec lcfg_pkgtools)

Optional Feature Support

Occasionally you might have a feature which you want to make optional at compile-time. The lcfg-nsu component, for example, provides the choice of whether to build with support for X. This can be done with a CMake cache variable:

# Allow the choice of whether or not to build with X11 support

set(XSUPPORT "on" CACHE BOOL "Build with X11 support")

if(XSUPPORT)
  # do some stuff ...
endif(XSUPPORT)

Setting CPP Macro Definitions at Compile Time

It is sometimes necessary to define a CPP macro as part of the build-process so that particular code features are activated. This can be done very easily using the CMake add_definitions() command, for example:

add_definitions(-DXSUPPORT)