The CMake Build Manager

January 1, 2003

CMake is an open-source, cross-platform C/C++ build manager that supports platform inspection and user-customized builds.

By William Hoffman and Ken Martin,  Dr. Dobb’s Journal
Jan 01, 2003
URL:
http://www.ddj.com/cpp/184405251

Many software projects ship with both a UNIX makefile (or Makefile.in) and a Microsoft Visual Studio workspace, requiring you to constantly keep both build systems up to date and consistent with each other. Targeting additional build systems (Borland or CodeWarrior, for instance) requires even more custom copies of these files, creating an even bigger problem. This problem is compounded if you try to support optional components, such as including JPEG support if libjpeg is available on the system. CMake, the build manager we present here, consolidates these different files into one simple and easy to understand file format.

CMake (available at http://www.cmake.org/) is an open-source, cross-platform C/C++ build manager that lets you specify build parameters in a text file. This text file is then used by CMake to generate files for native build tools such as Microsoft Visual Studio project files, UNIX-style makefiles, and Borland makefiles. In addition to specifying which files should be compiled into shared libraries, static libraries, or executables, CMake supports platform inspection and user-customized builds.

Even software that isn’t cross platform can make use of CMake because it provides the ability to:

  • Automatically search for programs, include paths, and libraries that may be required by the software being built. This includes the ability to consider environment variables and registry settings when searching.
  • Build in a directory tree outside of the source tree. This is a useful feature that is common on UNIX platforms; CMake provides this feature on Windows as well.
  • Create complex custom commands for automatically generated files such as QT’s moc (http://www.trolltech.com/), ITK’s CABLE (http://public.Kitware.com/cable/), and VTK’s (http://www.kitware.com/) wrapper generators. These commands are used to generate new source files during the build process.
  • Select optional components at configuration time. For example, in VTK several of the libraries are optional, and CMake provides an easy way for users to select which libraries are built.
  • Automatically generate workspaces and projects from text files, which can be handy for systems that have many programs or test cases, each of which requires its own project file, typically a tedious manual process to create.
  • Easily switch between static versus shared or DLL builds. CMake knows how to create shared libraries and Windows DLLs on all the platforms supported by CMake. Complicated linker flags are built-in and enable advanced features like built-in run-time paths for shared libraries on many UNIX systems.
  • Automatic generation of file dependencies and support for parallel builds on all UNIX systems.

When developing cross-platform software, CMake also provides:

  • The ability to determine byte order and other hardware-specific characteristics.
  • A single set of build files that work on all platforms. This avoids the problem of developers having to maintain the same information in several different formats inside a project.
  • The ability to configure .h files with system-dependent information, such as the location of data files and other information. CMake can create header files that contain paths to data files and other information in the form of #define macros. System-specific flags can also be placed in configured header files. This has advantages over command-line -D options to the compiler because it lets other build systems use the CMake-built library without having to specify the same command-line options used during the build.

Before developing CMake, we made do with a number of existing tools. For instance, autoconf combined with automake provide much of the same functionality as CMake, but using these tools on Windows requires the installation of many additional tools not found natively on a Windows box. In addition to requiring a host of tools, autoconf can be difficult to use or extend and impossible for some tasks that are easy in CMake. Even if you do get autoconf and its required environment running on your Windows box, it generates makefiles that force users to the command line. CMake, on the other hand, generates Visual Studio project files that can be used directly from the IDE that Windows developers are accustomed to. CMake also provides for automated dependency generation that is not done directly by autoconf.

Other tools such as ANT, qmake, and JAM have taken different approaches to solving these problems and have helped us to shape CMake. Of the three, qmake is the most similar to CMake although it lacks much of the system interrogation that CMake provides. Qmake’s input format is more closely related to a traditional makefile. ANT and JAM are also cross platform although they do not support generating native project files. They do break away from the traditional makefile-oriented input with ANT using XML and JAM using its own language.

Other projects use existing scripting languages such as Perl or Python to configure build processes. Although similar functionality can be achieved with systems like that, overuse of tools can make the build process more of an Easter egg hunt than a simple-to-use build system. Before installing your software package, users are forced to find and install Version 4.3.2 of this, and 3.2.4 of that, then start the build process. To avoid this problem, CMake needs only the tools required by the software that it is building. At minimum, using CMake requires a C compiler and the compiler’s native build tools, and a CMake executable. CMake was written in C++ and requires only a C++ compiler to build. 

Using CMake

CMake’s input consists of text files called “CMakeLists.txt” in each source directory. This input file specifies the things that need to be built in the current directory. CMakeLists.txt consists of one or more commands, each of the form COMMAND(args…), where COMMAND is the name of the command, and args consists of a whitespace-separated list of arguments to the command. (Arguments with embedded whitespace can be quoted.) Typically, there will be a CMakeLists.txt file for each directory of the project. Consider building “hello world”: You would have a source tree with the files Hello.c and CMakeLists.txt. The CMakeLists.txt file would contain two lines:

PROJECT(Hello)

ADD_EXECUTABLE(Hello Hello.c)

The PROJECT command indicates what the name of the resulting workspace should be, and ADD_EXECUTABLE adds an executable target to the build process. That’s all there is to it for this simple example. If your project requires a few files, it is still easy—just modify the ADD_EXECUTABLE line to:

ADD_EXECUTABLE(Hello Hello.c File2.c File3.c File4.c)

ADD_EXECUTABLE is just one CMake command.

Now consider the more complicated Listing One, in which the SET command is used to group together source files into a list. The IF command is used to add either WinSupport.c or UnixSupport.c to this variable. Finally, the ADD_EXECUTABLE command is used to build the executable with the files listed in the variable HELLO_SRCS. The FIND_LIBRARY command looks for the Tcl library under a few different names and in a few different paths, and if it is found, adds it to the link line for the Hello executable target. (The # character denotes a comment line.)

CMake defines some variables for use within CMakeList files. For example, WIN32 is always defined on Windows systems and UNIX is always defined for UNIX systems.

For many projects, converting them into CMake is straightforward as well. For example, in VTK, the PNG library (http:// www.libpng.org/pub/png/) has been incorporated into the build process via CMake. Listing Two is the CMakeList.txt file that does this.

Running CMake

Once you have created CMakeList.txt files, you are ready to invoke CMake. CMake can run from either the command line or a GUI. On Windows, the GUI looks like Figure 1. On UNIX systems, there is a curses text-based interface that looks like Figure 2. The curses interface is run from the command line with the source directory as the single command-line argument; if the option is omitted, the current directory is used as the source directory and an in-source build is performed.

The Windows GUI lets you specify the source and build directories for the project with a directory browser or entry box. It also saves the last 10 build and source directories, so it is easy to work on several projects at the same time. The source directory is the location of the CMakeLists.txt files and the project source code. The build directory is the location where the binary files will be kept. It does not have to be the same as the source, but could be. The GUI also allows the user to choose Visual Studio 6, Visual Studio 7 (.NET), Visual Studio NMake, or Borland makefiles for output. The curses interface produces UNIX makefiles.

Once the source, build, and target output selections have been made, press the Configure button and CMake will read the CMakeLists.txt files in the source directory, collect the user-configurable variables, and display them in the Cache Values section of the interface. If the configure process changes a variable (or displays a new one), it is displayed at the top of the list and colored red, or with an * by it in the curses version. Since changing a variable may cause new ones to appear, you should press Configure again until the values are no longer red.

This multiple pass configuration helps to control the number of options you see. For example, the Visualization Toolkit (VTK) is a C++ Library that can optionally be wrapped into Tcl. Wrapping VTK into Tcl requires a number of Tcl libraries, header file paths, and other tools to be specified. CMake does not display any of those values until you have selected the VTK_WRAP_TCL option. Once that has been selected, the additional Tcl options show up in CMake. This effectively hides that complexity from developers who are not using Tcl. Once users are satisfied with all the selections, they press either the OK button or g (for “generate”). This causes CMake to write out the project or makefiles into the build directory.

CMake can also run from the command line. This is useful for automated nightly builds and regression testing. Running CMake from the command line is similar to running configure scripts generated from autoconf. The major difference is that CMake does not take many options on the command line; rather, those options can be specified to CMake by editing the CMakeCache.txt file. So, to run “cmake” from the command line, you can either run it in the source directory with no options for an in-source build, or you can run it in a build directory specifying the source directory on the command line. For example, to build “project hello” out of source, you use:

mkdir hello-build

cd hello-build

cmake ../hello

The CMake Cache

CMakeCache.txt stores all the options for a particular project. The first time cmake is run, it creates this file. For each project, there is only one cache file located at the root of the build directory. The values in the cache are created from the execution of the commands found in the input files. For example, the FIND_LIBRARY command creates a variable called TCL_LIBRARY. A typical entry in the cache file looks like this:

//What is the path where the file tkWinInt.h can be found

TK_INTERNAL_PATH:PATH=D:/VTK/ Rendering/tkInternals

The entry specifies the variable on the left, along with a GUI hint type, and a value to the right.

Testing with CMake

CMake also includes integrated support for regression testing. The ADD_TEST command is used to define a test to run. A test is specified by a name, an executable to run, and a list of arguments to pass to the executable. To run the tests for a project, the utility program “ctest” is included with CMake. This program will run all of the tests in the current directory and any subdirectories. When CMake produces makefiles, a test target is generated so users can simply type “make test” to have all the tests run. CMake also supports DART testing (http://public .kitware.com/Dart). Dart allows for HTML to be generated, summarizing the build and test results each night.

Targets Produced

In addition to the targets to build the object files, libraries, and executables of a project, CMake adds some additional useful targets. In Microsoft project files, CMake generates a utility project called “ALL_BUILD” that depends on all of the libraries and executables in a project and can be selected as the active project to build everything in the system. This is required because CMake allows for extra utility projects to be put in the system that you may not want to run each time with the Batch build facility of Visual Studio. In addition, the ALL_BUILD target makes it easier to select between configurations in a large project. The Batch build facility requires you to select the configuration for each library or executable by hand. This can be done once for all targets using CMake’s ALL_BUILD target.

With makefiles, you can type “make test,” which causes all the tests specified with ADD_TEST in the current directory or lower to be run by ctest. Finally, the make install target performs a UNIX-style installation of the software based on the INSTALL_TARGETS command. The targets allow most any project to support the traditional UNIX process of:

make

make test

make install

Dependencies are maintained automatically by cmake, but a dependency check can be forced with a make depend command.

Inside CMake

The CMake architecture is simple and easy to extend. It consists of a C++ class for each command using the Command pattern (see Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al., Addison-Wesley, 1995). The cmMakefile class stores the parsed contents of the CMakeList.txt files and a generator class exists for each build system supported. The generator is responsible for producing the platform-specific build files such as UNIX makefiles or MSVC projects. Currently, there are five generators in CMake—UnixMakefileGenerator, MSProjectGenerator, MSDotNETGenerator, NMakeMakefileGenerator, and BorlandMakefileGenerator; see Figure 3.

The commands are self documenting, with virtual functions that return documentation strings. A utility program uses all the existing commands to generate the HTML that is included in the documentation for CMake. For a full reference of all the commands, see the CMake reference manual. Because each command is a class, it is easy to extend CMake with additional commands in the future.

DDJ

Listing One

SET(HELLO_SRCS Hello.c File2.c File3.c)
IF (WIN32)
    SET(HELLO_SRCS ${HELLO_SRCS} WinSupport.c)
ELSE (WIN32)
    SET(HELLO_SRCS ${HELLO_SRCS} UnixSupport.c)
ENDIF (WIN32)
ADD_EXECUTABLE (Hello ${HELLO_SRCS})

# look for the Tcl library
FIND_LIBRARY(TCL_LIBRARY NAMES tcl tcl84 tcl83 tcl82 tcl80 
  PATHS  /usr/lib /usr/local/lib)
IF (TCL_LIBRARY)
  TARGET_ADD_LIBRARY (Hello ${TCL_LIBRARY})
ENDIF (TCL_LIBRARY

 

Listing Two 


# source files for png
SET(PNG_SRCS
pngget.c    pngrio.c   pngwrite.c
png.c       pngmem.c   pngrtran.c   pngtrans.c   pngwtran.c
pngerror.c  pngpread.c pngrutil.c   pngvcrd.c    pngwutil.c
pnggccrd.c  pngread.c  pngset.c     pngwio.c
)
IF(WIN32)
  IF(BUILD_SHARED_LIBS)
    ADD_DEFINITIONS(-DPNG_BUILD_DLL -DZLIB_DLL 
                                         -DPNG_NO_MODULEDEF)
  ELSE(BUILD_SHARED_LIBS)
    ADD_DEFINITIONS(-DPNG_STATIC)
  ENDIF(BUILD_SHARED_LIBS)
ENDIF(WIN32)

ADD_LIBRARY(png ${PNG_SRCS})
TARGET_LINK_LIBRARIES(png zlib)

INSTALL_TARGETS(/lib/vtk png)

Figure 1: CMake Windows GUI.

 

Figure 2: CMake UNIX text-based interface.

Figure 3: CMake architecture.

Bill and Ken work at Kitware and can be contacted at kitware@kitware.com.

Leave a Reply