Simple build system using CMake

Before starting this out, let me say that this is the second step in the “Project from scratch” series that I prepared on my YouTube channel (/c/cppdev) and you can find the step by step video here.

Again, this is mostly based on Linux Ubuntu distribution since I find it more suited for C++ development (and programming in general).

Now that we got this out of our way, let’s get started.

CMake. I am sure that, if you had some work to do in C++ until now, you already heard about CMake at least once. When you are creating any kind of project, you will need a build system in place so that you don’t always use a huge instruction using the compiler.

Just to add some insight, the compiler is the tool that actually ‘compiles’ your project – so, it generates the binary or library starting from your source code. CMake is just a build tool, that will be using the compiler in order to generate the binary/library or do other steps that are needed for the building of your project.

Usually, if you use an IDE, you will not need to create your own build system, since the IDE will take care of that, but in my opinion, it is good to know how the steps are performed in the background, so you have a better understand about all the development environment.

One thing to note, is that CMake is actually cross platform. So, if it is configured correctly, it can be used for building your project in both Linux or Windows environment.

So, in order to create our own build system with CMake, we need to perform some steps.

1.CMake, Make and compiler tool installation

For this tutorial, we are going to use g++ as the compiler which is a tool provided by the GNU Compiler Collection. We need to install both CMake utility and g++ utility.

Aside from this, we will also need Make utility because CMake is actually using Make in the back end.

The process will be : CMake will generate all the configuration needed for Make files. Then, we will use Make in order to actually compile our project based on the configuration generated.

We are going to do this in on simple line (in Linux Ubuntu): sudo apt-get install cmake make g++

After this is done, we have all the tools necessary in order to build our project using CMake and g++. Easy as that.

2.CMake configuration overview and structure

In order to configure CMake for our project, we need a file called CMakeLists.txt. When we run CMake binary for a path in which a CMakeLists.txt file exists, the instructions from that file will be executed.

In our case, if we have a project with the following directory structure:

We are going to have the following CMakeLists.txt structure:

So, we are going to create a CMakeLists.txt for each directory that contains any kind of source code (.cpp files) that should result in a binary/library. In our case, the contents of the src directory will result in the main binary (e.g. project binary) and the contents of the test directory will result in a test binary (e.g. project-test binary).

Also, we need to have “global” CMakeLists.txt in the main directory for our project. This global CMakeLists.txt configuration file will be used every time when we try to build the project with CMake. From here, we will include(or not) the subdirectories for src and test, which in turn will execute all the instructions from the CMakeLists.txt in these directories.

If we include subdirectory src in the global CMakeLists.txt, then aside from all the instructions from the global CMakeLists.txt, all the instructions from the src/CMakeLists.txt directory will also be executed.

Since we have the actual CMake files structure, we only have to add some contents/instructions to them, so our build system knows how to build our project when we use CMake utility.

3.CMake configuration file contents (implementation)

For the contents of the CMakeLists.txt files I will just add it here and will comment every single line with “#”. I think this should give a good understanding of what happens.

cpp-tutorial/CMakeLists.txt

# minimum version of cmake utility - usually the one in which this file was created
cmake_minimum_required(VERSION 3.20)

# set the project name
set(PROJECT_NAME "CPP Tutorial")

# set the PROJECT_ID which will be the target name by changing every
# non-alphanumeric character in PROJECT_NAME to an underscore
string(MAKE_C_IDENTIFIER "${PROJECT_NAME}" PROJECT_ID)
# i.e. CPP Tutorial -> CPP_Tutorial

# change the PROJECT_ID to lower case
string(TOLOWER ${PROJECT_ID} PROJECT_ID)
# i.e. CPP_Tutorial -> cpp_tutorial

# set the project for CMake
project(${PROJECT_NAME})

# include the CMakeLists.txt in src
# this will run everything in that file
add_subdirectory(src)

# add option for building tests
option(BUILD_TESTS "Build tests also for ${PROJECT_NAME}" OFF)

# if tests should be build
# add the subdirectory(with CMakeLists.txt) for tests
if(BUILD_TESTS)
	add_subdirectory(test)
endif()

cpp-tutorial/src/CMakeLists.txt

# set a variable that contains the list
# of source files to be built for our targets
set(${PROJECT_ID}_SOURCES
	Board.cpp
	Game.cpp
	main.cpp
)

# the PROJECT_ID target shall be an executable
add_executable(${PROJECT_ID})

# set the source files to be built into the executable
target_sources(${PROJECT_ID}
	PRIVATE
		${${PROJECT_ID}_SOURCES}
)

# provide private include paths for our executable
target_include_directories(${PROJECT_ID}
	PRIVATE
		../include
)

# set the C++ 20 standard
target_compile_features(${PROJECT_ID}
	PRIVATE
		cxx_std_20
)

# clear the ${PROJECT_ID}_SOURCES variable
# so we don't pollute everything
unset(${PROJECT_ID}_SOURCES)

Now that we are done with the global CMakeLists.txt and the src/CMakeLists.txt, let’s see about the CMakeLists.txt for the test binary (in the test directory).

We will just build another executable for now only with the source code in the test directory.

cpp-tutorial/test/CMakeLists.txt

# set a variable that contains the list
# of source files to be built for our targets
set(${PROJECT_ID}_TEST_SOURCES
	BoardTest.cpp
	GameTest.cpp
	main.cpp
)

# the ${PROJECT_ID}-test target shall be an executable
add_executable(${PROJECT_ID}-test
	${SRC_TEST_FILES}
)

# set the source files to be built into the executable
target_sources(${PROJECT_ID}-test
	PRIVATE
		${${PROJECT_ID}_TEST_SOURCES}
)

# provide private include paths for our executable
target_include_directories(${PROJECT_ID}-test
	PRIVATE
		../include
)

# set the C++ 20 standard
target_compile_features(${PROJECT_ID}-test
	PRIVATE
		cxx_std_20
)

Everything is now set for our simple build system and we can just use it in order to build our project.

4.Actual compilation and building (with CMake and Make)

In order to compile and build our project, we need both CMake and Make utilities.

We also want to create a different directory where the build files will be generated so we don’t just overload our project.

First of all, we need to create a new directory called build and from inside that directory we can call CMake first, then Make and we will have everything generated there.

If we want to build only the source code, we are not going to provide the “BUILD_TESTS” option to CMake. That will make it so that the default value for it will be taken – in our case, OFF. Any CMake option can be provided using the syntax "-D<option=value>". In our case, if we want to build the test cases, the syntax will be "-DBUILD_TESTS=ON".

After the CMake files are generated, we are just going to use "make" to build everything.

Build without the test cases – after creating the "build" directory – with "mkdir" command:

Build with the test cases (the test binary is also generated) – still in the "build" directory:

We have both the “cpp_tutorial” main executable (binary) and also the “cpp_tutorial-test” test executable (binary).

All of this can also be done, concatenating everything into a single command:

mkdir build && cd build && cmake .. -DBUILD_TESTS=ON && make

Source code

You can find the source code of this article, together with everything else from “Project from scratch” on my github.

Also, maybe you want to also check out a small scale CMake example, in my other article here.

That’s all folks

That’s it for this simple CMake build system. As I was earlier specifying, the best thing about using CMake for your project, is that, it is cross platform. You can configure the CMake files so that you can build for both Linux and Windows environments.

I hope you learned something new from this one. Don’t forget to also take a look at my YouTube channel, maybe you will find more details that could interest you.

6 thoughts on “Simple build system using CMake”

  1. Pingback: Simple build system using Make – The CPPDEV

  2. Pingback: Simple build system using Bitbake – The CPPDEV

  3. Pingback: GTest and GMock integration with CMake – The CPPDEV

  4. Pingback: Build system with modern CMake - cppdev

  5. Pingback: GTest and GMock integration with CMake - cppdev

  6. Pingback: Simple build system using Make - cppdev

Leave a Reply

%d bloggers like this: