I have had to use many different kinds of build tools and found CMake as my favorite. Sure, there are other cross-platform tools like Automake, Gradle, and qmake, but for me each have little annoyances. Such as behaving differently across different versions of Linux, have complex syntax, overly focused on specific programming language, or often not emitting useful error messages for debugging build scripts.

CMake can help with:

  • Find dependent libraries
  • Easily perform out-of-source incremental builds
  • Generate project files for IDEs, such as Visual Studio or Xcode
  • Execute shell commands to perform post-build testing or operations, such as uploading binaries

Basic CMake File

CMake generates build environment by looking at a text file named CMakeLists.txt. You can keep these text files in sub-directories of your source tree. A simple CMakeLists.txt at the root of your source tree can look like:

# CMake comments start with #, these lines are ignored.
project(WackyMango)
cmake_minimum_required(VERSION 2.8)
 
# use set() to assign values to a variable
set(SOURCES a.cpp b.cpp c.cpp)
 
# include more CMakeLists.txt in other directories
add_subdirectory(A)
add_subdirectory(B)
 
# specify where the header files are
include_directories(/usr/local/include)
 
# specify where the libraries are
link_directories(/usr/local/lib)

In the example above, SOURCES contains a list of C++ source files. We can build different types of binearies from them by using the following statements:

# For an executable
add_executable(... ${SOURCES})
 
# For a shared library
add_library(... SHARED ${SOURCES})
 
# For a static library
add_library(... STATIC ${SOURCES})

Notice how you can reference values using the variable with ${ }. The ... is the output name, on Windows you'll have .exe appended automatically to programs. Libraries will have proper prefix and extension added (that is, CMake will generate platform-specific name.dll, libname.so, or libname.dylib).

Dependency Tracking

The name in add_executable() and add_library() call is a target that can be used for dependency tracking. If you have libB.so that requires libA.so to link:

add_library(A SHARED ${A_SOURCES})
add_library(B SHARED ${B_SOURCES})
add_dependencies(B A)

The lines above established that B depends on A, and so A will always be built first, even if you perform parallel builds (for example, make -j6).

Generating Build Environment

To perform an out-of-source build, create a build directory and run cmake and reference the root CMakeLists.txt from source directory. At this point you can also use the -G switch to generate specific build environment.

For example, CMake on Windows generate Visual Studio solutions by default. But you can also generate makefiles for use with nmake, like so:

mkdir %USERPROFILE%\build
cd %USERPROFILE%\build
cmake -G "NMake Makefiles" %USERPROFILE%source\
nmake

Summary

The general steps to create build system using CMake:

  • Write CMakeList.txt
  • Run cmake in build directory to setup build environment
  • Run make or nmake to build from command line, or use an IDE such as Visual Studio or Eclipse

Next up, CMake for multi-target, cross-platform builds.