May 24, 2010

Simple CMake example

Here is a simple example of how to use CMake to automatically generate Makefile on Linux system.

Suppose $PREFIX is the top level directory of my project where all of my sources, object files and executable reside. The CMake way of maintaining a regular project is to split all the files into two distinct sets with each one represented by a directory.

Usually, we have $PREFIX/src and $PREFIX/bin, where src contains all the source files and bin contains all the binary files. Source files do not necessarily mean program source code, it could also contain other stuff, like a bump map or texture files if you are doing graphics, or audio files if you are doing multimedia. Most importantly, the CMake system configuration file, which is called CMakeLists.txt, is located under the src directory.

The bin directory is where all the automatically generated files are located during the compile and link process. On different platforms, this directory may contain quite different types of files.

In my example, my source code is just one simple file, called mpisort.cc and of course there is also the CMakeLists.txt file. So the src directory looks like this,

src/mpisort.cc
src/CMakeLists.txt

mpisort.cc is a program implementing parallel quick sort algorithm through MPI which is almost the de facto standard for parallel programming for distributed memory systems like large clusters. The exact implementation is not important here, two things are important, however. They are

(1) MPI requires special compile flags, which could be quite different on different platforms.
(2) MPI requires special link flags and corresponding libraries, which too could be very different for different platforms.

The way CMake works is that you tell CMake (through its own language) which library you use and what flags you use. One nice thing is that there are so many built-in macros in CMake that address those frequent jobs, so what I need to do here is just to write down those several lines in the CMakeLists.txt file:

1 cmake_minimum_required(VERSION 2.8)
2 find_package(MPI REQUIRED)
3 include_directories(${MPI_INCLUDE_PATH})
4 add_executable(mpisort mpisort.cc)
5 target_link_libraries(mpisort ${MPI_LIBRARIES})

The first line tells CMake the minimum possible version of CMake for this CMakeLists.txt to work properly. This is largely for backward compatibility.

The second like tells CMake to find a MPI library on the target system, this will be automatically addressed differently on different platforms accordingly. The REQUIRED flag causes CMake to blow out if it cannot find a working MPI library.

If CMake successfully locates the information for MPI, the MPI_INCLUDE_PATH and MPI_LIBRARIES macros will be defined to be the exact path where "mpi.h" is found and the necessary libraries against which the program needs to link.

The third line informs CMake to include in search path those additional paths represented by MPI_INCLUDE_PATH where "mpi.h" can be found.

The fourth line tells CMake the name for the executable and its associated source code. Please note CMake will automatically handle naming issues. For example, on Linux, the executable is named "mpisort" with proper file attribute correctly set. On Windows, this executable will be "mpisort.exe", the ".exe" is correctly resolved by CMake when I build the project on a Windows platform.

The fifth line lets CMake know which libraries to link when generating the executable.

And we are done in terms of CMake configuration. Now we need to let CMake generate a Makefile for us so that we can compile the program. Remember I said the bin directory contains all those automatically generated files during compile and link. So now let us switch to our $PREFIX/bin directory. And the way we use CMake is like this,

ccmake ../src/

ccmake is a curses interface for CMake on Linux. It takes a directory as argument, and looks for a CMakeLists.txt file under that directory to start with. It parses the file for configuration information and displays its interface then. In its interface, customized configuration is supported, since my sample program is so simple, its does not require any additional config. I then press 'C' to let cmake identify the necessary system configurations, like the C compiler's name, location, version and that of C++ compiler, etc. (I do not know why this is not done automatically on startup). I then press 'C' again to let cmake parase my user-defined configration (even if in my case there is nothing customized, I have to do this). Now cmake gathers all necessary information and is ready to generate a Makefile for us. I then press 'G' to let it output the Makefile. ccmake will automatically exits after generating the Makefile. So I come back to the command line again, now I can do make to compile and link my executable.

This might not be very straightforward for my toy program, however, it is quite nice for intermediate or large projects.

Moreover, the CMake way is portable, you can copy the src directory to Windows or Mac and basically follow the same steps to generate Visual Studio project/solution files and XCode project files respectively without any modification of the sources.

For more detailed information, please refer to http://www.cmake.org/

If you need my example, it is here.