Introduction

This article provides a straightforward set of “Hello World!” introductions to using CMake for building C++ projects. All steps are performed using Linux on the BeagleBone platform, but the instructions are relevant to most Linux platforms.

The make utility and Makefiles provide a build system that can be used to manage the compilation and re-compilation of programs that are written in any programming language. I use Makefiles quite often in my projects to automate the build process; however, there are times when Makefiles become overly complex for the task — particularly when building projects that have multiple sub directories, or projects that are to be deployed to multiple platforms.

Building complex projects is where CMake really shines — CMake is a cross-platform Makefile generator! Simply put, CMake automatically generates the Makefiles for your project. It can do much more than that too (e.g., build MS Visual Studio solutions), but in this discussion I focus on the auto-generation of Makefiles for C/C++ projects.

Get Source Code

Source Code for this Discussion

Get Source Code

All of the code for this discussion is available in the GitHub repository for the book Exploring BeagleBone. The code can be viewed publicly at: the ExploringBB GitHub CMake Example Projects directory, and/or you can clone the repository on your BeagleBone (or other Linux device) by typing:

Example 1: The Hello World Example

The first project is contained in the extras/cmake/helloworld directory of the GitHub repository. In this example a simple “Hello World” C++ program is built (HelloWorld.cpp), which has the source code provided in Listing 1.

Listing 1: The Hello World C++ Example

There is only one other file required in the same directory, CMakeLists.txt, which contains the contents of Listing 2.

Listing 2: The Simple Project CMakeLists.txt file

 The CMakeLists.txt file in Listing 2 consists of only three lines:

  • The first line sets the minimum version of CMake for this project, which is major version 2, minor version 8, and patch version 9 in this example. This version is somewhat arbitrary in this example, but providing a version number allows for future support for your build environment. Therefore, you should use the current version of CMake on your system, which for this example is determined just below.
  • The second line is the project() command that sets the project name.
  • The third line is the add_executable() command, which requests that an executable is to be built using the helloworld.cpp source file. The first argument to the add_executable() function is the name of the executable to be built, and the second argument is the source file from which to build the executable.

To build the project, first test that you have CMake installed, and if not, install it using the package manager that is used by your flavor of Linux. For example, under Debian:

molloyd@beaglebone:~/$ sudo apt-get install cmake

molloyd@beaglebone:~/$ cmake -version
cmake version 2.8.9

The project code is in the GitHub repository directory, where you will see only the two files described in Listings 1 and 2 above:

molloyd@beaglebone:~$ cd ~/exploringBB/extras/cmake/helloworld/
molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ ls
CMakeLists.txt helloworld.cpp

Now you are ready to build the Hello World project using CMake — execute the cmake command and pass it the directory that contains the source code and the CMakeLists.txt file — in this case “.” refers to the current directory:

molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ cmake .
-- The C compiler identification is GNU 4.6.3
-- The CXX compiler identification is GNU 4.6.3
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/molloyd/exploringBB/extras/cmake/helloworld

CMake identified the environment settings for the Linux device and created the Makefile for this project, which can be viewed. Do not make edits to this Makefile, as any edits will be overwritten the next time that the cmake utility is executed.

molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ ls
CMakeCache.txt CMakeFiles CMakeLists.txt Makefile cmake_install.cmake helloworld.cpp
molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ ls -l Makefile
-rw-r--r-- 1 molloyd molloyd 4811 Apr 1 01:52 Makefile
molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ more Makefile
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 2.8

# The shell in which to execute make rules.
SHELL = /bin/sh

Once the Makefile has been created, the make command can be used to build the project:

molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ make
Scanning dependencies of target hello
[100%] Building CXX object CMakeFiles/hello.dir/helloworld.cpp.o
Linking CXX executable hello
[100%] Built target hello
molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ ls -l hello
-rwxr-xr-x 1 molloyd molloyd 7335 Apr 1 01:54 hello
molloyd@beaglebone:~/exploringBB/extras/cmake/helloworld$ ./hello
Hello World!

It works! However, this process is a somewhat excessive way to just build the HelloWorld.cpp program. It is important though, because it explains the basic operation of CMake. We can now examine more complex CMake examples.

Example 2: A Project with Directories

As your project grows, it is likely that you will organize it into sub-directories. Makefiles become more verbose when there are sub-directories present — in fact, it is usual practice to place an individual Makefile in each sub-directory. These Makefiles are then individually invoked by the Makefile in the parent directory.

CMake can be very useful in this situation. In this example, a project with a typical directory structure is used. The tree utility program displays below the structure of the example project (note: this student test project is available in the GitHub repository that is described above):

molloyd@beaglebone:~/exploringBB/extras/cmake/student$ tree
.
|-- CMakeLists.txt
|-- build
|-- include
| \-- Student.h
\-- src
  |-- Student.cpp
  \-- mainapp.cpp
3 directories, 4 files

You can see in this example that any header files (.h) are placed in the include directory and that the source files (.cpp) are placed in the src directory. I have also created a build directory (which is currently empty) that is used to contain the final binary executable and any temporary files that are required for the build. The CMakeLists.txt file for this project in Listing 3 is only slightly different than that in Listing 2 above:

Listing 3: The Multi-directory Project CMakeLists.txt file

The important changes in Listing 3 are as follows:

  • The include_directories() function is used to bring the header files into the build environment.
  • The set(SOURCES … ) function can be used to set a variable (SOURCES) that contains the name values of all of the source files (.cpp) in the project. However, because each source file must be added manually the next line is used in its place, and this line is commented out.
  • The file() command is used to add the source files to the project. GLOB (or GLOB_RECURSE) is used to create a list of all of the files that meet the globbing expression (i.e., “src/*.cpp“) and add them to a variable SOURCES.
  • The add_executable() function uses the SOURCES variable, rather than an explicit reference to each source file, in order to build the testStudent executable program.

For this example, I wish to place all of the build files in the build directory, which is achieved very simply by calling the cmake program from within the build directory, as follows:
molloyd@beaglebone:~/exploringBB/extras/cmake/student$ cd build
molloyd@beaglebone:~/exploringBB/extras/cmake/student/build$ cmake ..
-- The C compiler identification is GNU 4.6.3
-- The CXX compiler identification is GNU 4.6.3

-- Build files have been written to: /home/molloyd/exploringBB/extras/cmake/student/build

The build directory then contains the Makefile for the project, which correctly refers to the files in the src and include directories. The project can then be built from the build directory using the make command:

molloyd@beaglebone:~/exploringBB/extras/cmake/student/build$ ls
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake
molloyd@beaglebone:~/exploringBB/extras/cmake/student/build$ make

molloyd@beaglebone:~/exploringBB/extras/cmake/student/build$ ls
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake testStudent
molloyd@beaglebone:~/exploringBB/extras/cmake/student/build$ ./testStudent
A student with name Joe

One nice feature of this approach is that all of the files related to the build process are within the build directory as illustrated by the tree utility output below: 

To clean this project you can simply recursively delete all files/directories within the build directory, for example:
molloyd@beaglebone:~/exploringBB/extras/cmake/student/build$ cd ..
molloyd@beaglebone:~/exploringBB/extras/cmake/student$ rm -r build/*
molloyd@beaglebone:~/exploringBB/extras/cmake/student$ tree

.
|-- CMakeLists.txt
|-- build
|-- include
| \-- Student.h
\-- src
. |-- Student.cpp
. \-- mainapp.cpp
3 directories, 4 files

which is the same file system structure as was present before the cmake program was executed.

Important: If you add new source files to your project it is very important that you call the cmake program again, otherwise the Makefiles will not be updated to account for any additions.

Example 3: Building a Shared Library (.so)

In this example a shared library is built using the project code in Example 2. The project is almost the same, except that the mainapp.cpp file is removed, as it is not relevant to a library build. Therefore, the shared library only contains a single Student class, however, that is sufficient to demonstrate the principles of building a library using CMake. The directory structure of the project is as follows:

molloyd@beaglebone:~/exploringBB/extras/cmake$ tree studentlib_shared/
studentlib_shared/
|-- CMakeLists.txt
|-- build
|-- include
| \-- Student.h
\-- src
. \-- Student.cpp
3 directories, 3 files

Again in this project, header files are placed in the include directory and source files are placed in the src directory. The empty build directory is used to contain the final binary library and any temporary files that are required for the build. The CMakeLists.txt file is provided in Listing 4.

Listing 4: The Multi-directory Shared Library Build CMakeLists.txt file

The important changes for this example are as follows:

  • The set(CMAKE_BUILD_TYPE Release) function is used to set the build type to be a release build.
  • Instead of the add_executable() function that is used in previous examples, this example uses the add_library() function. The library is built as a shared library using the SHARED flag (other options are: STATIC or MODULE) , and the testStudent name is used as the name of the shared library.
  • The last line uses the install() function to define an installation location for the library (in this case it is /usr/lib). Deployment is invoked using a call to sudo make install in this case.

In this example the library is built in the build directory, which results in the output:

molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared$ cd build/
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared/build$ cmake ..
-- The C compiler identification is GNU 4.6.3

molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared/build$ make
Scanning dependencies of target testStudent
[100%] Building CXX object CMakeFiles/testStudent.dir/src/Student.cpp.o
Linking CXX shared library libtestStudent.so
[100%] Built target testStudent
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared/build$ ls -l *.so
-rwxr-xr-x 1 molloyd molloyd 7503 Apr 1 21:36 libtestStudent.so

You can use the ldd command to display the shared library dependencies:
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared/build$ ldd libtestStudent.so
libstdc++.so.6 => /usr/lib/arm-linux-gnueabihf/libstdc++.so.6 (0xb6ea6000)
libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6e3a000)
libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb6e16000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d31000)
/lib/ld-linux-armhf.so.3 (0xb6f6b000)

The CMakeLists.txt file also includes a deployment step, which allows you to install the library in a suitable accessible location. Shared library locations can be added to the path, or if you wish to make them available system wide you can add them to the /usr/lib directory. For example, the libtestStudent.so library can be installed system wide using:

molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared/build$ sudo make install
[sudo] password for molloyd:
[100%] Built target testStudent
Install the project…
-- Install configuration: "Release"
-- Installing: /usr/lib/libtestStudent.so
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_shared/build$ ls -l /usr/lib|grep libtest*
-rw-r--r-- 1 root root 7503 Apr 3 14:23 libtestStudent.so

This step has to be performed with root access in order to write to the /usr/lib directory. You will also find a file in the build directory, called install_manifest.txt that describes the locations at which the make install command applied changes.

Example 4: Building a Static Library (.a)

A statically-linked library is created at compile time to contain all of the code code relating the library — essentially it makes copies of any dependency code, including that in other libraries. This results in a library that is typically larger in size than the equivalent shared library, but because all of the dependencies are determined at compile time, there are fewer run-time loading costs and the library may be more platform independent. Unless you are certain that you require a static library, you should use a shared library (Example 3) as there will be fewer code duplications and the shared library can be updated (e.g., for error correction) without recompilation.

To build a static library using CMake, the steps are almost exactly the same as for Example 3 above. The code for this example is available in exploringBB/extras/cmake/studentlib_static/ and the CMakeLists.txt file is provided in Listing 5 below.

Listing 5: The Static Library CMakeLists.txt file

Use the same steps as before to build the static library, and you will see the output as follows:
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_static$ cd build/
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_static/build$ cmake ..
-- The C compiler identification is GNU 4.6.3

molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_static/build$ make
Scanning dependencies of target testStudent
[100%] Building CXX object CMakeFiles/testStudent.dir/src/Student.cpp.o
Linking CXX static library libtestStudent.a
[100%] Built target testStudent
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_static/build$ ls -l lib*
-rw-r--r-- 1 molloyd molloyd 3320 Mar 2 01:50 libtestStudent.a

You can determine the constituents of a static library using the GNU ar (archive) command — for example:
molloyd@beaglebone:~/exploringBB/extras/cmake/studentlib_static/build$ ar -t libtestStudent.a
Student.cpp.o

You can also use the GNU nm command to list the symbols in object files and binaries. In this case, the command lists the symbols in the student library and their types (e.g., T is code, U is undefined, R is read-only data). This information can be very useful for debugging any problems that may occur with static libraries.

Example 5: Using a Shared or Static Library

Once a library has been developed using the steps described in Example 3 or Example 4, the next question is how do you use the library in your projects? CMake can be used to generate the Makefiles in your project in order to simplify this process.

Listing 6 provides the source code for a CMakeLists.txt file that can be used to build a program that links to a library (either shared or static). For this example the shared library that is generated in Example 3 is used and a short C++ program is written that utilizes the functionality of that library. This C++ code is provided in Listing 7 and in the directory exploringBB/extras/cmake/usestudentlib/.

Listing 6: The CMakeLists.txt file for building a C++ program that uses a library

Listing 7: An Example C++ Program

The project can be built and executed using the following steps:

molloyd@beaglebone:~/exploringBB/extras/cmake/usestudentlib$ tree
.
|-- CMakeLists.txt
|-- build
\-- libtest.cpp
1 directory, 2 files
molloyd@beaglebone:~/exploringBB/extras/cmake/usestudentlib$ cd build
molloyd@beaglebone:~/exploringBB/extras/cmake/usestudentlib/build$ cmake ..
-- The C compiler identification is GNU 4.6.3

molloyd@beaglebone:~/exploringBB/extras/cmake/usestudentlib/build$ make
Scanning dependencies of target libtest
[100%] Building CXX object CMakeFiles/libtest.dir/libtest.cpp.o
Linking CXX executable libtest
[100%] Built target libtest
molloyd@beaglebone:~/exploringBB/extras/cmake/usestudentlib/build$ ls -l libtest
-rwxr-xr-x 1 molloyd molloyd 7706 Apr 2 21:07 libtest
molloyd@beaglebone:~/exploringBB/extras/cmake/usestudentlib/build$ ./libtest
A student with name Joe

Conclusions

The examples above provide a short and practical introduction to CMake and how it can be used to build: a simple project, a separately compiled project, and a shared library. These are the operations that you are likely to perform and the examples above can act as templates. However, it is also likely that you will require functionality that is not listed in this short discussion. The best, and most up-to-date documentation on CMake is available at the www.cmake.org website. In particular, the CMake Documentation Index provides a very useful list of available commands. The document is available at: CMake 3.0 Documentation Index