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.
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)
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")
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.
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)
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})
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
.
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.
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})
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 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})
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
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})
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})
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)
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)
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)