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.

Source Code for this Discussion

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

C++

1

2

3

4

5

6

#include<iostream>

intmain(intargc,char*argv[]){

std::cout<<"Hello World!"<<std::endl;

return0;

}

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

1

2

3

cmake_minimum_required(VERSION 2.8.9)

project (hello)

add_executable(hello helloworld.cpp)

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:

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:

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.

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):

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

1

2

3

4

5

6

7

8

9

10

11

12

13

cmake_minimum_required(VERSION 2.8.9)

project(directory_test)

#Bring the headers, such as Student.h into the project

include_directories(include)

#Can manually add the sources using the set command as follows:

#set(SOURCES src/mainapp.cpp src/Student.cpp)

#However, the file(GLOB...) allows for wildcard additions:

file(GLOB SOURCES "src/*.cpp")

add_executable(testStudent ${SOURCES})

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:

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:

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.

#Set the location for library installation -- i.e., /usr/lib in this case

# not really necessary in this example. Use "sudo make install" to apply

install(TARGETS testStudent DESTINATION /usr/lib)

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:

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:

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

cmake_minimum_required(VERSION 2.8.9)

project(directory_test)

set(CMAKE_BUILD_TYPE Release)

#Bring the headers, such as Student.h into the project

include_directories(include)

#However, the file(GLOB...) allows for wildcard additions:

file(GLOB SOURCES "src/*.cpp")

#Generate the static library from the sources

add_library(testStudent STATIC ${SOURCES})

#Set the location for library installation -- i.e., /usr/lib in this case

# not really necessary in this example. Use "sudo make install" to apply

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

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

Share This Story, Choose Your Platform!

Dr. Derek Molloy is a senior lecturer in the School of Electronic Engineering, Faculty of Engineering and Computing, Dublin City University, Ireland. He lectures at undergraduate and postgraduate levels in object-oriented programming with embedded systems, digital and analog electronics, and 3D computer graphics. His research contributions are largely in the fields of computer and machine vision, 3D graphics, embedded systems, and e-Learning. This is his personal blog site.

Everything else I’ve seen seems needlessly complicated. People look for a build system to make it simpler. A complete compiler call is a lot of typing in all but the most trivial examples. You have delivered a necessary service.

Great tutorial! It was really well explained. How about a part 2? I would like to know how to use cmake for a project with multiple modules or subdirectories. And then each subdirectory would create a shared library where a main program would link to. The main program would reside in the same project as the shared libraries.

Not that cmake is hard to understand , but for a beginner like me , was hard to find even understand the basic stuff mentioned in most of the site. Dr.Derek Molloy Thanks a ton for sucha user friendly and simple tutorial ! It helped me a lot .

Thanks for this! Have a question about the part about using the library: I followed the instructions and have an include, src, build and all the files. When doing “make”, I get the following message: /usr/bin/ld: cannot find -ltestStudent. What does this mean? Thank you!

After reading a hundred other tutorials, there where more question-marks than answers in my head. I almost lost hope for ever finding a decent explanation on how to write a cmakelists.txt… … and then I found yours. *.* Your tutorial helped a lot. Thanks!

I have looked at several cmake tutorials and was about to give up because none of them make sense. It is just beyond my comprehension. There are series of commands and it is not clear what exactly they are doing. Some would give very complicated use case with the equally complicated CMakeLists.txt! Your tutorial is the only one which made perfect sense to me! Thank you very much! In case you have other tutorials especially in embedded system, I would be very happy to know.

Leave A Comment

The "monster" image that is associated with your comment is auto-generated -- it makes it easier to follow the conversation threads. If you wish to replace this image
with a less (or perhaps more) monstrous version, add an image at Gravatar.com against the e-mail address that you use
to submit your comment. Your image will henceforth be used on most WordPress sites. Please note that I will remove any messages that contain blatant advertisement or that refer to
illegal software, content etc. I may tidy up some messages if they contain code dumps etc. E-mail addresses are used only to notify you of any responses, and to authenticate your future comments
on this website -- they are not made public nor used for any other purpose. See the Privacy
and Cookie Policy for a full description. I manually approve all new posts in order to keep the website spam free, but once your post is
approved, all future posts should be automatically approved. Please let me know if your messages do not appear. I really appreciate it when you answer the questions of others on
the page, as it is difficult for me to do so and continue to produce new content. Thanks for your understanding, Derek.

Archives

About:

This site brings together all of the video content on the Derek Molloy YouTube channel and structures it so that you can follow the videos as lessons. It also integrates associated documentation, datasheets and tools to allow you to get the best from the video series. It also has a blog to allow me to post new videos, articles and useful information that may not be in video form