Integrating with CMake
In the previous guide, we went through how to set-up the project files for a simple calculator app. We went through how to build and run both the application and the tests by hand, invoking the compiler manually.
In this guide, we are going to see how to automate the build and run process with CMake and Make. After completing this guide, you will be able to build complex C++ applications with multiple source files and multiple test suites.
Pre-requisites
To complete this guide, you must have a working installation of CMake. CMake is a widely used, powerful build system. Check the guides in the official CMake website to learn more about how to install and configure CMake in your machine.
Setup
This guide starts from the last point of the first guide (Writing your first test). If you haven't gone through the first guide, its recommended you go through that content first.
After completing the first guide, the directory structure should look like this:
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
│ └ calculator.test.cpp
├ /lib
│ └ cest
└ /build
├ calculator_test
└ calculator
Creating the Makefile
Let's start by creating a simple Makefile recipe to automate the build, test, run and clean processes. We will place the Makefile at the root directory of the project. After creating the file, the directory structure should look like this:
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
│ └ calculator.test.cpp
├ /lib
│ └ cest
├ /build
│ ├ calculator_test
│ └ calculator
└ Makefile
With a simple clean
rule, we can dispose of the compiled files from the previous guide.
- The
clean
build target is set as aPHONY
build target, because we want to indicate Make that this target should not follow the dependency resolution mechanisms of Make. Check the Make documentation to know more.
Next, we will add a rule to build all the source files in the project. Since we will be using CMake to run the compilation process, the build rule will just invoke CMake and compile the resulting Makefile generated by CMake:
build:
cd build
cmake .. # (1)
make # (2)
clean:
@rm -rf build/*
.PHONY: clean build
- CMake is invoked in the project parent directory, where the CMakeLists.txt file will reside. We will go through its contents in next sections.
- After CMake has completed preparing the project, we will have a Makefile available in the build directory. Invoking this Makefile will build the project.
Finally, we will add two extra rules to execute the application and the tests, respectively. This will simplify the build and run process a lot from now on, as we will be able to run all tests with a single command:
run: build # (1)
./build/app
test: build
@find $(pwd)/build -name "test_*" -type f -executable -print0 | xargs -0 -I % sh -c % # (2)
build:
cd build
cmake ..
make
clean:
@rm -rf build/*
.PHONY: clean build run test
- Both the
run
andtest
targets depend on thebuild
target, as the project must be compiled prior to running the application or the tests. - To run all the tests, we look for all executable files in the
build
folder namedtest_*
. Then, found files are executed.
CMake rules to build the application
To be able to use CMake to build our project, we must create a set of rules CMake will follow to create the required dependency graph and be able to build all executables in the project. This rules are defined in CMakeLists.txt
files.
CMake is a complex and powerful language, check CMake's official website to learn more about it and find available bibliography.
In this guide, we will create a very simple CMakeLists.txt
file that will let us get started. We will create the file in the root directory of the project. After creating it, the directory structure should look like this:
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
│ └ calculator.test.cpp
├ /lib
│ └ cest
├ /build
│ ├ calculator_test
│ └ calculator
├ CMakeLists.txt
└ Makefile
Let's start by building the minimum set of rules to compile the calculator application into an executable. The generated binaries will be split in two parts: on one hand, the business logic will be inside a static library (libcalculator.a
).
On the other hand, the entrypoint of the application (main.cpp
) will be compiled independently to generate the application's executable, linking against the static library.
This will allow us to create each test suite as an independent executable file, linking to the static library to be able to access all the business logic from the test suite.
cmake_minimum_required(VERSION 3.5)
project(Calculator)
set(CALCULATOR_SOURCES src/calculator.cpp)
add_library(calculator ${CALCULATOR_SOURCES})
add_executable(app src/main.cpp)
target_link_libraries(app calculator)
After creating the CMake rules, test the whole set by cleaning and building the project. To do so, simply run:
Afterwards, the directory structure should be like:
/calculator
├ /src
│ ├ main.cpp
│ ├ calculator.cpp
│ └ calculator.h
├ /test
│ └ calculator.test.cpp
├ /lib
│ └ cest
├ /build
│ ├ ... Many files generated by CMake
│ ├ libcalculator.a
│ └ app
├ Makefile
└ CMakeLists.txt
Both the static library containing the business logic (libcalculator.a
) and the application (app
) have been compiled.
CMake rules to build the tests
Now that all the application's business logic is being compiled into a static library, creating new test suites is as simple as linking each test suite file with the static library. This will generate a new executable with the test suite.
The CMake rules to include the test suite from the previous example (calculator.test.cpp
) would be like the following:
cmake_minimum_required(VERSION 3.5)
project(Calculator)
set(CALCULATOR_SOURCES src/calculator.cpp)
add_library(calculator ${CALCULATOR_SOURCES})
add_executable(app src/main.cpp)
target_link_libraries(app calculator)
add_executable(test_calculator test/calculator.test.cpp)
target_link_libraries(test_calculator calculator)
After running the compilation process, the test_calculator
test suite runnable file will be compiled.
Next reading
After completing this guide, you have seen how to integrate Cest Framework with CMake and automate the compilation of test suites and the application.
CMake is a complex topic, and there is enormous flexibility on what you can achieve with it.
In the next section, you will learn how to use Cest Framework to test C code, adding powerful semantics to C testing.