Yet another attempt on a cmake intro...

(2014-10-30)

CMake is the current fashion in build systems. It is not perfect (e.g. the learning curve) but better than its predecessors.

Some advantages of cmake:

(if you know how to use them...)

Resources on cmake

Ok, so let us start with the very basics. Why in hell we do we need this ?

Edit-Compile-Link-Execute-Fail-Edit-Compile...

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

Edit

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!

Compile

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

Working with larger projects

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:

On UNIX (and MacOSX), Makefiles are used to describe the dependencies between the pieces of code.

Fail...

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 to the rescue!

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

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

Out-Of-Source Workflow

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.

ninja

Just for demonstration, we perform a build with ninja. Ninja is much faster than make, and it can work efficiently in parallel. So it makes a difference in large projects. However it currently does not work for projects containing Fortran code.

$ 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 test

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