2022-09-08: Updated links etc.
Ninja now works for fortran as well.
2021-11-11: Transfer to franklin+github.io
2014-10-30: Initial version
(if you know how to use them...)
Cross platform portability. Linux, MacOSX, Windows are fully supported.
Interaction with native build tools (UNIX make, MacOSX xcode, Visual studio).
Inbuilt support of unit tests.
Easy handling of out-of-source builds.
"CMake for busy scientists" by Radovan Bast turned into the CMake cookbook
Ok, so let us start with the very basics. Why in hell we do we need this ?
This is the infinite Karma cycle of the programmer who works with classical compiled languages like C/Fortran/C++. With the widespread adoption of interpreted languages (in particular matlab) it is not obvious that even university alumni have been exposed to this workflow. Still, compiled languages with respect to portability and speed are the best choice. It is not excluded that this situation will change in the not so distant future... which is not here yet.
The source code for the demo test can be found in tinyproject.zip
So let us start with a small program in a file hello.c
:
#include <stdio.h>
int main (int argc, char *argv[])
{
printf("Hello world!\n");
return 0;
}
It can be compiled, linked and executed from the command line:
$ cc -o hello hello.c
$ hello
Hello world!
Now, our program shall do something more useful, by calling a function from a module described in infinitecycle.h
int infcyc(int start); /* call infinite cycle and return result*/
and infinitecycle.c
:
#include "infinitecycle.h"
int infcyc(int start)
{
int result;
result=start;
for (;result<42;result++);
return result;
}
Our program now looks like
#include <stdio.h>
#include "infinitecycle.h"
int main (int argc, char *argv[])
{
int answer;
answer=infcyc(0);
printf("Hello world, the answer is %d!\n",answer);
return 0;
}
To put this together, we call
$ cc -o hello hello.c infinitecycle.c
$ hello
Hello world, the answer is 42!
The cc
command above performs several process steps. It compiles the code into object files which already contain something like the machine code.
$ cc -c -o hello.o hello.c
$ cc -c -o infinitecycle.o infinitecycle.c
These object files have to be linked together and with system libraries containing e.g. the printf
function and other voodoo. The program which performs this step is called linker or loader (ld
on Unix):
$ ld -o hello hello.o infinitecycle.o -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib64/gcc/x86_64-suse-linux/4.8/../../../../lib64/crt1.o /usr/lib64/gcc/x86_64-suse-linux/4.8/../../../../lib64/crti.o /usr/lib64/gcc/x86_64-suse-linux/4.8/crtbegin.o -L/usr/lib64/gcc/x86_64-suse-linux/4.8 -L/usr/lib64/gcc/x86_64-suse-linux/4.8/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib64/gcc/x86_64-suse-linux/4.8/../../../../x86_64-suse-linux/lib -L/usr/lib64/gcc/x86_64-suse-linux/4.8/../../.. -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib64/gcc/x86_64-suse-linux/4.8/crtend.o /usr/lib64/gcc/x86_64-suse-linux/4.8/../../../../lib64/crtn.o
Larger collections of object files can be put together into libraries (archives), just for the example we make an archive of one object file:
$ ar cvr libfinite.a infinitecycle.o
a - infinitecycle.o
These archives can be used to link with them rather than with the collection of object files they contain. In order not to have to write all the voodoo shown above, it is customary in Unix that the compiler commands know how to link the code, so it suffices to invoke
$ cc -o hello hello.o libfinite.a
Now this is essentially the way one puts together larger projects, possibly from different sources, e.g. a graphics library with some numerical code. The result of the installation of the library is that we have somewhere in our system the header (.h
) files which describe to the compiler how to interface the code, and a library (.a
) or shared library (.so
) which contains the code. Also, for a larger project we put the code written by ourselves onto libraries. When developing, we want to have two things at once:
after changing something, we want to have some magic which repeats the compilation and linking process automatically
we do not want a recompile of everything after changing only a tiny bit of code
On UNIX (and MacOSX), Makefiles are used to describe the dependencies between the pieces of code.
In order to work properly, all dependencies have to be written into such a file. Cumbersome, and error prone, especially for larger projects... E.g. we have to trace all the #include
statements and write the corresponding dependencies into the Makefile.
CMake essentially is the tool which automates this process. At the same time it can generate other dependency descriptions as well, e.g. for ninja.
CMakeLists.txt
for project descriptionCMake relies on a project description in a file CMakeLists.txt
. For larger projects, this file is much easier to write than a Makefile
. In particular, it automatically traces all dependencies. By default, it generates a standard Unix Makefile which is then used for the real build.
So let us try this for our tiny project. We add the following file CMakeLists.txt
to our project directory:
#
# Minimal version required for CMake
#
cmake_minimum_required(VERSION 2.8)
#
# Project name and languages used
# may be C, CXX and FORTRAN
#
project(tiny C)
#
# Add source file(s) to a library
#
add_library(finite infinitecycle.c)
#
# Define an executable depending on some source(s)
#
add_executable(hello hello.c)
#
# Add library(ies) to link with the executable
#
target_link_libraries(hello finite)
The following workflow is not the only one possible, but highly recommended. It is called out-of-source build and essentially boils down to the fact that everything which is generated during the build process is placed into the build directory. Nothing is created in the source tree. This is of high practical value if one works with different build configurations (debug/release), compilers or even operating systems from the same source directory.
$ mkdir build
$ cd build
$ cmake ..
$ cd ..
$ make -C build
$ .build/hello
Hello world, the answer is 42!
Now we found out that the cycle in infcyc
is finite. So we change the header file infinitecycle.h
(it is too late to change the name of the function as lots of people are using it. At least we remark this in the comment... ):
/* well, it is finite, but we don't want to break the API*/
int infcyc(int start); /* call infinite cycle and return result*/
We invoke the recompile:
$ make -C build
Scanning dependencies of target finite
[ 50%] Building C object CMakeFiles/finite.dir/infinitecycle.c.o
Linking C static library libfinite.a
[ 50%] Built target finite
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/hello.c.o
Linking C executable hello
[100%] Built target hello
This demonstrates that CMake built a Makefile for us which contains the dependency of hello.c
and infinitecycle.c
on infinitecycle.h
.
Just for demonstration, we perform a build with ninja. Ninja is a replacement of make which is much faster than make, and it can work efficiently in parallel. So it makes a difference in large projects.
$ mkdir build-ninja
$ cd build-ninja
$ cmake -G Ninja ..
$ cd ..
$ ninja -C build-ninja
$ build-ninja/hello
Hello world, the answer is 42!
The previous example could have used cmake -G "Unix Makefiles" ..
which is the default on Unix. Please remark as well that the build
and build-ninja
subdirectories coexist without problems.
Automatic tests are a great tool to verify the correctness of code modifications. CMake provides some infrastructure for this.
Let us add the following lines to CMakeLists.txt
#
# Enable testing
#
enable_testing()
#
# Run hello in test mode
#
add_test(universe hello -test)
and add a test mode to our program which now looks like
#include <stdio.h>
#include <string.h>
#include "infinitecycle.h"
int main (int argc, char *argv[])
{
int test_mode;
test_mode=0;
if ( argc>1 && (strcmp(argv[1],"-test")==0))
test_mode=1;
int answer;
answer=infcyc(0);
printf("Hello world, the answer is %d!\n",answer);
if (test_mode && answer!=42)
{
printf("broken universe\n");
return 1;
}
return 0;
}
And now
$make -C build
[ 50%] Built target finite
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/hello.c.o
Linking C executable hello
[100%] Built target hello
$make -C build test
Running tests...
Test project /home/fuhrmann/Wias/www/fuhrmann/drafts/tinyproject/build
Start 1: universe
1/1 Test #1: universe ......................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.00 sec