Introduction to CMake

Finally I can write about using CMake. I’m going to go through the basics of writing a CMakeLists.txt file, which controls how CMake runs, and using CMake to build software. Since my focus is on CMake itself, I’ll use a very simple program for testing that simply prints out the version of SDL that you are using. Let’s create that first.

Create a new directory named check-sdl-version to hold this project. I have mine in /home/eris/gamedev/projects/check-sdl-version on Linux, and C:\gamedev\projects\check-sdl-version on Windows. Under this, create subdirectories named src, include, build-linux, and build-windows. This will be the basic directory structure for all projects.

Now use your editor to create the file main.c in the src directory and add the following to it.

/******************************************************************************
//
// check-sdl-version
// Copyright (c) 2010 Eris Caffee
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//
//
// Just a quick test program to verify SDL installation and to use as an
// example for learning to use CMake.
//
******************************************************************************/

#include <stdio.h>
#include <SDL.h>
/* Redirect I/O on Windows to a file.  We would not need to do this in a
   console app. */

#ifdef WIN32
#include <windows.h>
#include <io.h>

void redirect_stdio(void)
   {
   /* We must freopen stderr to get it to have a valid fd before we can
      dup it to stdout.  This creates a file on disk, which we then delete. */

   freopen("stdout.txt", "w", stdout);
   freopen("stderr.txt", "w", stderr);
   if (_dup2(_fileno(stdout), _fileno(stderr)) != 0)
      fprintf(stdout, "_dup2 failed!\n");
   DeleteFile("stderr.txt");
   }
#endif

int main(int argc, char **argv)
   {
   SDL_version compiled;
   const SDL_version * linked;

#ifdef WIN32
   redirect_stdio();
#endif

   printf("\t\t\tCompiled\t\tLinked\n");

   SDL_VERSION(&compiled);
   linked = SDL_Linked_Version();
   printf("SDL version\t\t%d.%d.%d\t\t\t%d.%d.%d\n",
          compiled.major, compiled.minor, compiled.patch,
     linked->major, linked->minor, linked->patch);

   return 0;
   }
 

This is just a simple C program that uses the SDL_VERSION macro and SDL_LinkedVersion() function to display the version of SDL that the program was compiled against and the version that has actually been linked against at runtime. It must be run from the command line to see the output, of course.

Notice also the comment block at the top of the file with the program name, copyright notice, license information, and a brief description of the file. It’s a good habit to include this in all of your code, but to save space I won’t be showing it again after this. The license, by the way, is the MIT License and is a simple, permissive. open source license.

Next we need to create our CMakeLists.txt file. This is the file that tells cmake how to create the makefiles that will be used to control compiling our program. The mixed captialization of the filename is no accident. The file has be to be named exactly that way on Linux, although Windows doesn’t care about the case since it’s filesystem is inherently case-insensitive.

So open a file named CMakeLists.txt in the check-sdl-version directory itself, and add the following to it:

###############################################################################
#
# A generalized cmake file for developing cross-platform games.
#

cmake_minimum_required (VERSION 2.6 FATAL_ERROR)
#set (CMAKE_VERBOSE_MAKEFILE ON)

# Name your program!
set (App_Name "check-sdl-version")
if (App_Name STREQUAL "")
  message (FATAL_ERROR "You must set the App_Name variable!")
endif ()

# Every project must have a name.
project (${App_Name})

################################################################################
# Ensure that we are not building in our source directories.

set (Build_Dir_OK "TRUE")
string (REGEX MATCH "^${CMAKE_SOURCE_DIR}" In_Sub_Dir ${CMAKE_BINARY_DIR})
if (In_Sub_Dir)
  string (REGEX MATCH "^${CMAKE_SOURCE_DIR}/build" In_Build_Dir ${CMAKE_BINARY_DIR})
  if (NOT In_Build_Dir)
    set (Build_Dir_OK "FALSE")
  endif ()
endif ()

if (NOT Build_Dir_OK)
  message (FATAL_ERROR "You must run cmake from a directory that is not in your source tree, or that is in a special subdirectory of the tree whose name begins with ‘build’.")
endif ()

################################################################################
# Set up the basic build environment

if (CMAKE_BUILD_TYPE STREQUAL "")
  # CMake defaults to leaving CMAKE_BUILD_TYPE empty. This messes up
  # differentiation between debug and release builds.              
  set (CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE)
endif ()

################################################################################
# The core project files

file (GLOB SRCS src/*.c src/*.cpp)
file (GLOB HDRS include/*.h include/*.hpp)

link_directories (
  )

include_directories (
  ${CMAKE_SOURCE_DIR}/include
  )

if (WIN32)
  add_executable (${App_Name} WIN32
    ${SRCS}
    ${HDRS}
    )
else ()
  add_executable (${App_Name}
    ${SRCS}
    ${HDRS}
    )
endif ()

target_link_libraries (${App_Name}
)

################################################################################
# SDL Support

# if (WIN32)
#   set (ENV{SDLDIR} "c:/gamedev/deps/sdl/SDL-build")
# else ()
#   set (ENV{SDLDIR} "/home/eris/gamedev/deps/sdl/SDL-build")
# endif ()

option(Option_SDL_Dev "Build an SDL application." OFF)

if (Option_SDL_Dev)

  # SDL base package
  find_package (SDL)
  if (NOT SDL_FOUND)
    message (FATAL_ERROR "SDL not found!")
  endif (NOT SDL_FOUND)
  include_directories(
    ${SDL_INCLUDE_DIR}
    ${INCLUDE_DIRECTORIES}
    )
  target_link_libraries(${App_Name}
    ${SDL_LIBRARY}
    ${TARGET_LINK_LIBRARIES}
    )

endif (Option_SDL_Dev)
 

That may look a bit intimidating if you’ve never used CMake before, but it’s not so bad. Let’s go through it:

The lines that begin with a # character are comments. You can include comments on the same lines as actual cmake commands if you wish by ading a # at the end of the line along with you remarks.

cmake_minimum_required (VERSION 2.6 FATAL_ERROR)

Cmake lets you specify which version is required to be used. I specify version 2.6 because earlier versions of CMake require a syntax for IF/ELSE/ENDIF blocks where the condition is repeated in each statement, and that’s both awkward to type and confusing to read. The third parameter, FATAL_ERROR, is optional, but versions of CMake prior to 2.6 will not terminate if you don’t include it.

Notice that “VERSION” and “FATAL_ERROR” are in all capital letters. CMake operator and variable names are case sensitive, although the actual commands, such as “cmake_minimum_required” are not case sensitive. They used to be in earlier versions of CMake, so you will sometimes see CMake commands written in upper case.

#set (CMAKE_VERBOSE_MAKEFILE ON)

This line is commented out since you won’t normally need it, but enabling the CMAKE_VERBOSE_MAKEFILE option can help you debug the automatically generated Makefiles if your program won’t build correctly.

set (App_Name "check-sdl-version")
if (App_Name STREQUAL "") 
  message (FATAL_ERROR "You must set the App_Name variable!")
endif ()

To make this CMakeLists.txt file as general as possible, I define the program name in the variable App_Name. This is what will be used as the name of the final executable file, and on Windows it will have “.exe” appended to it automatically.

The syntax for assigning a value to a variable is set (VARNAME VALUE). You get the value of a variable by writing ${VARNAME}, but we didn’t do that in the if statement! When you write ${VARNAME} the CMake interpreter substitutes the value of the variable before evaluating the command. Thus if App_Name was blank, if (${App_Name} STREQUAL "") would get transformed into if ( STREQUAL "") and processing would halt on a syntax error. Use ${VARNAME} only when you need to insert the literal value of the variable into a command.

The message command prints your message for the user to read. The first parameter is optional and specifies the message type. FATAL_ERROR halts processing, but other options you might find useful include STATUS and WARNING. You can also include variables in messages, like this: message ("App_Name is ${App_Name}")

project (${App_Name})

And in this line we need to insert the literal value of a variable! Every CMake project needs a name, and you use the project command to set it. Projects do not need to have the same name as the application they build (after all a single project my create multiple exectuable programs), but in this case I am naming the project for the program just to keep things simple.

set (Build_Dir_OK "TRUE")
string (REGEX MATCH "^${CMAKE_SOURCE_DIR}" In_Sub_Dir ${CMAKE_BINARY_DIR})
if (In_Sub_Dir)
  string (REGEX MATCH "^${CMAKE_SOURCE_DIR}/build" In_Build_Dir ${CMAKE_BINARY_DIR})
  if (NOT In_Build_Dir)
    set (Build_Dir_OK "FALSE")
  endif ()
endif ()

if (NOT Build_Dir_OK)
  message (FATAL_ERROR "You must run cmake from a directory that is not in your source tree, or that is in a special subdirectory of the tree whose name begins with 'build'.")
endif ()

This section checks to make sure that you are not building the binaries in the same directory as your source files. This helps keep things clean and prevents binaries for different target platforms from getting mixed up together.

First we make a new variable Build_Dir_OK and set it to TRUE. According to the CMake manual, a boolean constant is true if “the constant is 1, ON, YES, TRUE, Y, or a non-zero number” and false “if the constant is 0, OFF, NO, FALSE, N, IGNORE, “”, or ends in the suffix ‘-NOTFOUND’.” A variable is considered true if its value is not a false constant.

Next we use the string command, which handles all string manipulation and comparison, to do a regular expression test of the built-in CMake variable CMAKE_SOURCE_DIR. This variable is the path of the top directory of the source tree. CMAKE_BINARY_DIR is where the programs will be built, and is by default the directory from which you run cmake (or ccmake or cmake-gui).

The syntax here is REGEX MATCH "pattern to match" RESULT_VAR "input". so in our case we are checking to make sure that CMAKE_BINARY_DIR is not a subdirectory of CMAKE_SOURCE_DIR. Do not be confused by the $ in the pattern – it’s not a regular expression special character! CMake pre-processes the line first, so that ${CMAKE_SOURCE_DIR} gets replaced by the actual directory name before the regular expression pattern is evaluated.

The result is stored in the variable In_Build_Dir as a boolean. If it’s true and we are in a subdirectory, we then check to see if that directorys name starts with “build”. If it does then we are OK. This lets us have subdirectories of our project named, for instance, build-linux, build-windows, or even builder-bob-the where we build our program for various target platforms.

After all this, if we are not in an acceptable build directory, we exit with a fatal error and message explaining the problem.

if (CMAKE_BUILD_TYPE STREQUAL "")
  # CMake defaults to leaving CMAKE_BUILD_TYPE empty. This messes up
  # differentiation between debug and release builds.               
  set (CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE)
endif ()

This bit of code sets the internal CMAKE_BUILD_TYPE variable to “RelWithDebInfo”, or Release With Debug Info. The build type determines what compiler options will be used. RelWithDebInfo means to compile with most optimizations and include debug info, Release is full optimzation and no debug info, Debug is no optimization and include debug info, and MinSizeRel means optimize for size and include no debug info.

file (GLOB SRCS src/*.c src/*.cpp)
file (GLOB HDRS include/*.h include/*.hpp)

The file command lets you manipulate files. You can read them, write to them, delete them, and even download files from a URL. We are using the GLOB function of the command to generate a list of files that match a pattern. This is a standard shell file matching pattern and we use it to create a list names, SRCS, that contains all of the C and C++ files in our src directory. Likewise, HDRS is set up to contain a list of all our header files.

link_directories (
)
include_directories (
  ${CMAKE_SOURCE_DIR}/include
)
if (WIN32)
  add_executable (${App_Name} WIN32
    ${SRCS}
    ${HDRS}
    )
else ()
  add_executable (${App_Name}
    ${SRCS}
    ${HDRS}
    )
endif ()

target_link_libraries (${App_Name}
)

The link_directories and include_directories commands specify where to look for libraries that we need to link against and include files used by our program. The arguments to these commands are lists of directories seperated by whitespace. I prefer to list each directory on its own line. Note that since my setup assumes headers files are in their own directory I list that directory in include_directories command.

add_executable finally defines an actual executable program to be created The first argument is the name of the program to create and the remaining arguments are the names of it’s source files.

We have two copies of the add_executable command wrapped in an if block. The variable WIN32 is provided by CMake and is true if we are building for Windows (including 64 bit Windows – the name is a bit unfortunate). If we are, we add the WIN32 option (unrelated to the WIN32 variable) to the add_exectuable comand to indicate that we want to create a graphical Windows program. Without this, CMake would generate a makefile to create a Windows console application.

When creating a WIN32 executable, we must provide a WinMain() function in our source code, which you may notice that we do not have. It’s all right, though, because SDL itself provides a basic one for you if you don’t provide one of your own.

By the way, I hope you saw that the else and endif commands have parentheses after them. These are required by CMake syntax. In fact, in earlier versions you were required to repeat the condition in each case, which led to confusing looking statements like this:

if (WIN32)
  do stuff for Windows
else (WIN32)
  do stuff for not Windows
endif (WIN32)

Avoiding the need for this is why I started off by requiring CMake 2.6 or later.

The target_link_libraries command must come after the add_executable command, and it is where you list the names of the libraries you want to link against. It’s arguments are the program name followed by a whitespace delimited list of libraries. The names are the base names of the libraries, so to link against libUtils.a on you would list simply “Utils”.

Now we come to one of the nicer aspects of CMake: find_package. This command loads a special CMake module that handles finding libraries and include files needed by your program. There are lots of packages supported out of the box, so before you go adding things by hand to the lists above, check to see if you can use a premade pacakge.

We are also going to get to use another cool CMake feature that lets you create options for your project.

option(Option_SDL_Dev "Build an SDL application." OFF)
if (Option_SDL_Dev)
...
endif (Option_SDL_Dev)

(In these very long if/endif blocks I go ahead and use the old form of including the condition in the endif statement so it’s obvious which if the endif belongs to.)

The option command tells CMake to create an option that you can select when running ccmake or cmake-gui, or activate on the command line of cmake. If the option is not active, its variable will be false, and you can test this in an if statement.

find_package (SDL)
  if (NOT SDL_FOUND)
    message (FATAL_ERROR "SDL not found!")
  endif (NOT SDL_FOUND)
  include_directories(
    ${SDL_INCLUDE_DIR}
    ${INCLUDE_DIRECTORIES}
    )
  target_link_libraries(${App_Name}
    ${SDL_LIBRARY}
    ${TARGET_LINK_LIBRARIES}
    )

Inside the Option_SDL_Dev if block, we call the find_package command with the argument “SDL”. This runs the FindSDL.cmake script, which is distributed as part of CMake. The script attempts to locate the SDL library and configure it for use. On success, the variable SDL_FOUND is set to true and SDL_INCLUDE_DIR and SDL_LIBRARY are set to values you can pass to the include_directories and target_link_libraries commands. When doing so, be sure to use the INCLUDE_DIRECTORIES and TARGET_LINK_LIBRARIES variables so that you add to the lists instead of overwriting them.
include_directories( ${SDL_INCLUDE_DIR} ${INCLUDE_DIRECTORIES} ) says to set the include directories list to the value of SDL_INCLUDE_DIR combined with the current value of INCLUDE_DIRECTORIES. You do not need to update the link directories.

The find_package command works well for libraries that are installed in standard locations, but you can also tell them explicitly where to look if, say, you have compiled a library on your own and installed it somewhere unusual. We have done exactly that with our SDL 1.3 library so we need to override the default location, and you can use an environment variable to do that.

Given the locations I installed SDL 1.3 in the previous article, I would use the following commands to tell CMake where to look for the library:

Linux:

$ export SDLDIR=/home/eris/gamedev/deps/sdl/SDL-build
$ ccmake ..

Windows:

 set SDLDIR=C:\gamedev\deps\sdl\SDL-build
> ccmake-gui ..

Windows with MSYS shell:

$ export SDLDIR=c:\gamedev\deps\sdl\SDL-build
$ ccmake-gui ..

Alternatively, if you would rather have the location put into the CMakeLists.txt file itself, add the following somewhere before the find_package call:

if (WIN32)
  set (ENV{SDLDIR} "c:/gamedev/deps/sdl/SDL-build")
else ()
  set (ENV{SDLDIR} "/home/eris/gamedev/deps/sdl/SDL-build")
endif ()

Make sure that you use the forward slash character – / – in the Windows path name and not the backslash. Even though Windows pathname components are separated by backslashes, CMake requires you to either use forward slashes in your CMakeLists.txt files, or to write double backslashes, like this: C:\\gamedev\\deps\\sdl\\SDL-build. CMake uses the backslash as it’s escape character so that you can include C-like escape sequences, such as \n for newline, in strings. A double backslash lets you insert a literal (and single) backslash.

ENV lets you access environment variables. So to retrieve the SDLDIR environment variable you use $ENV{SDLDIR} and to set it you use ENV{SDLDIR} without the extra $. (Remember, the $ causes the value of the variable to be substituted for the expression.) Setting an environment variable only affects the value that CMake knows about, of course. It does not change the actual environment variable you see from the shell.

Unfortunately, on Windows you will need to set the SDLDIR variable either in the CMakeLists.txt file or in the environment. This is because there are no standard locations for installing the headers and library files on Windows. For the moment, I am setting it in the environment on my systems.


Well! That was long, but now we should have a good CMakeLists.txt file. So let’s actually use it to build our project. To recap, I am assuming that have the following directories and files:

check-sdl-version/
  |-- CMakeLists.txt
  |-- build-linux/
  |-- build-windows/
  |-- include/
  |-- src/
        |-- main.c

Linux

Open a command prompt window and change to the build-linux directory. Set the SDLDIR environment variable if you need to with export SDLDIR="/home/eris/gamedev/deps/sdl/SDL-build" (using your own location, of course). Now run the command ccmake .. and that is not a typo – two letter c’s on that command. ccmake is a text-mode menu-driven program that let’s you select options and update variables before actually generating the makefiles for your project. The first time you run it the top of the scrren will simply says “EMPTY CACHE” telling you that CMake has no saved information about this project.

Type “c” to take a first pass at configuring the project. This will do a first parse of your CMakeLists.txt file. Once it’s done, use the arrow keys to navigate down to the Option_SDL_Dev setting and hit Enter to change it from OFF to ON. This is the option we created ourselves to find the SDL libraries.

Now type “c” again to update the configuration and CMake will now show you the information it found about SDL. Type “c” once agian to confirm the configuration (you could edit the values displayed first, if you needed to) and you will notice a new option has appeared at the bottom: “g” for “generate and exit”. This is what tells cmake to actually create the makefiles for your project, so type “g” now.

Assuming there were no errors, you should be back at the command prompt where you can run make to actually compile and link the program. Then you can run it as ./check-sdl-version and you should get output like this:

                        Compiled                Linked
SDL version             1.3.0                   1.3.0

Quick summary of the commands:

ccmake ..
make
./check-sdl-version

Windows

On Windows things follow the same general pattern, but differ in specifics. As before, open a command prompt and change to the build-windows directory.

For the Windows cmd.exe shell:
Set the environment variable if you need to with a command like set SDLDIR=c:\gamedev\deps\sdl\SDL-build (don’t use quotes around the path) and then run cmake-gui .. to launch the Windows equivalent of ccmake. This is a graphical program that lets you configure your projects and generate the makefiles. At the moment it shows your project directory and binary directory, which it determined based on the directory your were in when you ran the program, and the directory you specified on the command line “..”, the one above your starting directory.

Click the “Configure” button and you will be prompted to select the “generator” you want to use. CMake is capable of creating makefiles for many different compilers, and since there is no default compiler on Windows, you need to specify the one you want to use. Since we are running from the cmd.exe shell and using the MinGW compiler, we need to select the “MinGW Makefiles” option in the drop down list, and the “Use native compilers” option below that.

Now click “Finish” and CMake will build it’s initial cache as it did above. Once it’s done that you will see the same things listed as you saw when running ccmake on Linux, the Option_SDL_Dev setting in particular. Check it’s checkbox. and click “Configure” again to update. Click “Configure” one final time to confirm the configuration and you can then click “Generate” to actually create the makefiles. Then exit the program.

Back at the command prompt run mingw32-make and the program will be built for you. Before we can run it, though, we need to copy SDL.dll into the directory so the progrma can find it. copy %SDLDIR%\bin\SDL.dll will do that.

Now run the program as check-sdl-version. The output will be in a file named stdout.txt, which you can quickly view with the command type stdout.txt from the prompt.

In summary, run these commands:

set SDLDIR=C:\gamedev\deps\sdl\SDL-build
cmake-gui ..
mingw32-make
copy %SDLDIR%\bin\SDL.dll
check-sdl-version
type stdout.txt

For the MSYS shell:
If you compile from MSYS, the commands are similar to what you would use in Linux. First export the SDLDIR variable. Then run cmake-gui .. and use it as above, except you need to choose “MSYS Makefiles” in the first dialog box after you click “Configure”. Run make, copy the SDL.dll file into the directory, and run the program. These are the commands:

export SDLDIR="C:\gamedev\deps\sdl\SDL-build"
cmake-gui ..
make
cp $SDLDIR/bin/SDL.dll .
./check-sdl-version
cat stdout.txt

In both cases on Windows the output should be the same as shown above for the Linux program.


We now have a basic build system that we can use to manage our projects. The next thing to do is actually make something non-trivial!

Errata: I originally wrote the Windows pathname C:\gamedev\deps\sdl\SDL-build in the CMakeLists.txt file, but this is wrong. Regardless of what OS you are running CMake on, all pathnames must either use Unix style forward slashes, or you must write double backslashes. I have corrected the article and download file.

Download the full project source: check-sdl-version.tar.gz