What are build systems? CMake?

When I just got into the business of Software Engineering, everything was new to me and I have to say it was quite overwhelming. Having a background in Embedded Systems, gave me programming skills, but I never understood why build systems or unit tests where that important. Who needs tests? When you can just debug the code, while running, right? Oh man, I couldn’t be more wrong!

So first is first, what are build systems? To put it in a simple way, build systems are software tools used to automate the compilation process of a project.

Getting to the practical side, I thought it would be a good idea to set up a simple example project with the CMake build system. This build system is widely used in C++ projects as many IDE’s support CMake projects.

Cmake.svg

So what’s cmake? Well according to Wikipedia:

CMake is a cross-platform free and open-source software application for managing the build process of software using a compiler-independent method. It supports directory hierarchies and applications that depend on multiple libraries. It is used in conjunction with native build environments such as make, Apple’s Xcode, and Microsoft Visual Studio. It has minimal dependencies, requiring only a C++ compiler on its own build system.

Well that clears up everything doesn’t it? It didn’t for me! What you first need to realize is that CMake itself doesn’t not compile any code of your project. It is even a layer above it and that’s what makes it so powerful! While setting up a project with CMake, the developer scripts the configuration it wants for the project. These configuration can then be used by CMake to generate make files for a directory.

Any code base can be divided into several groups, like source code (domain), external libraries (thirdparty), test code (unit-testing) and User interface components. With using CMake it comes in handy to have a build directory, in which all the generated files (object files, executable, Makefiles) can be placed.

A structure that could be used for a CMake project can look like:


├── build dir
├── CMakeLists.txt
├── src
|	├── CMakeLists.txt
|	├── main.cpp
|	├── example.h
|	├── example.cpp
|	├── example2.h
|	└── example2.cpp
├── test
|	├── CMakeLists.txt
|	├── exampleTest.cpp
|	└── example2Test.cpp
├── ui
|	├── CMakeLists.txt
|	├── exampleView.h
|	└── exampleView.cpp
└── lib/thirdparty
	├── CMakeLists.txt
	├── UI dir
	|	├── exampleUi.h
	|	└── exampleUi.cpp
	└── Test dir
		├── exampleTest.h
		└── exampleTest.cpp

This is a very simple example for a structure! Libraries can be way more complex and can include .so files for example, but to keep it simple, we are just showing the header and source files.

When you look at the structure, there is a CMakeLists.txt configure file per directory. This configuration file can be used on it’s own to compile the source files in a directory to a target (executable or library). This a modulair set up as the target of the code in a directory can be executed on it’s own when it doesn’t have too many dependencies. This whole process is executed by the local generator.

An example of the CMakeLists.txt in src:


cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR)

set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_FLAGS -std=c++11 -Wall)

set(HEADERFILES
      example.h
      example2.h)

set(SRCFILES
      main.cpp
      example.cpp
      example2.cpp)

# Can also add libraries (dependencies, but for now we 
# will leave it at this.

add_executable(${PROJECT_NAME} 
               ${HEADERFILES} 
               ${SRCFILES})

So at the beginning of the file, the minimum required CMake version is set. When this version is a version lower than 3.10.0 then CMake will give a fatal error.

CMake has all these variables that can be used that can tell the compiler what to do. This makes it really easy to set compiler flags or the type of release you want to build.

It is also possible to set your own variables, as is done with header files and the source files that you want to have included in the executable.

The only thing that needs to be done is putting everything together in order to create a target. In this case an executable is created with the add_executable() command. The name of the executable is set by the de-referencing of the project name variable.  The same thing is done for the inclusion of the files by de-referencing the variables that were previously set.

The CMakeLists.txt example of src can also be created in a similar manner for the other directories. For example, the test directory. After all the directories have their own CMakeLists.txt files, a global configuration file can be set.

This will look as following:


cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR)
project(example)

add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(ui)
add_subdirectory(lib)

The global generator is then responsible for managing the configuration and generation of all the makefiles for the project.  The project command in placed in the top configuration file because it sets a name for the project and it comes in handy when using the PROJECT_SOURCE_DIR variable. But more on that later.

So there you have it, a simple CMake project set up that is ready to start building the targets that you require! I hope this will help you all out. Happy coding!

 

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *