# Hints on CMake use   {#cmake}

## Introduction

As of early 2021, a modified project structure and build system for
DUECA was completed. The old scripts (`dueca-project`) used CVS
(Concurrent Version System) as a back-end for version control, and an
elaborate structure with Makefiles and scripts to compile and link the
code of DUECA projects.

There were a number of reasons to change this, first and foremost is
the aging status of CVS, and while not becoming unavailable, fewer and
fewer people know how to use it.

Another issue is the work needed when using the Makefile system. Most
DUECA programmers will have encountered a point where a missing
library or header is preventing a successful DUECA build. When
borrowing a module from another project, often the main project
Makefile needs to be adjusted to link associated libraries.

## The dueca-gproject script

A new script was created, `dueca-gproject`, that differs from the old
script in a number of ways:

- The script uses git as back-end for the version control. In contrast
  to dueca-project, pure version control commands are not included in
  the script, as those can be performed by running git directly. Only
  specialised actions, like adding the files generated by the script
  and the particular way of checking out a git repository is performed
  by the script.

- The build system relies on the build tool CMake to produce generated
  code from the .dco files and compile and build the application.

- The project configuration is slightly improved. The old
  comm-objects.lst files are still there, but other configuration is
  moved to a hidden folder labeled `.config`. This folder contains:

  + A tiny text file called `machine`, which defines which
    configuration is checked out (the "machine class"). A machine
    class can for example be "ig", for Image Generator, computers
    configured with typically scene graph software to generate the
    outside image.

  + An xml file called `machinemapping.xml`, which defines which dueca
    node (computer in a lab or equivalent), runs which kind of
    machine.

  + A folder `class`, which lists one or more sub-folders, each named
    after a machine class. Each of these sub-folders contains two
    files. The first is `modules.xml`, which defines which modules
    (DUECA components) are used in a specific machine
    configuration. The second is `config.cmake`, an additional
    configuration file for the cmake build system. This file is
    normally unchanged, only for specific machine classes (notably
    computers with hardware attached to them, and specific libraries
    to access that hardware), it is used to list which hardware
    Input/Output libraries are to be linked with the project.

- There is also a folder `build` in the main project
  directory. Initially it is empty, it is used to store all build
  products (generated files, compiled code, and ultimately the DUECA
  executable). KEEP THIS FOLDER CLEAN. Don't put your files in
  here, everything here will be automatically generated from your
  source files elsewhere.

## Leveraging the power of CMake

For finding different libraries or support programs, CMake provides a
large range of functions that you can call. These functions set
variables in your workspace, you can then use these variables to
indicate where include files are located and which libraries should be
linked. A nice feature of CMake's detection is that it can also warn
you when libraries or headers are not found.

A commonly used function in CMake is `pkg_check_modules`, which you
can run with all libraries and programs that have so-called pkg-config
files. You find these pkg-config files in folders like

- `/usr/lib/pkg-config`
- `/usr/lib64/pkg-config`
- `/usr/lib/x86_64-linux-gnu/pkg-config`

Another common function is `find_package`, which can be used for
libraries and packages that have specific cmake configuration files
written for them. Check the folder `/usr/share/cmake/Modules` to check
with `Find` files are provided by the cmake installation.

If you have a library or header files for which these facilities are
not provided, you can use cmake's basic functions `find_path` and
`find_library` to search for header installation and libraries.

The final section of this chapter shows some common detection patterns.

## Overview of CMake files

A DUECA project uses three types of CMake files; a main file for the
whole project, a specific included file for a machine class, and two
variants of files for the module directories and the comm-objects
folder.

### Main CMakeLists.txt file.

You will find the main `CMakeLists.txt` file in the main project
directory (e.g., MyProject/MyProject). This file defines which
scripting language is used, and defines the DUECA components that are
used in the project, on all different machines. Generally no
modifications are needed for this file.

### Machine class config.cmake file.

A DUECA project can be built for different machine classes. A machine
class defines which modules are to be included in the compilation, and
optionally which specific libraries are needed for that machine class.

The `config.cmake` file describes adaptations specific for a machine
class. Modifications are often not needed. Only for specific machines,
and in our example these are often the control loading computers in
the simulators, that interact with a slew of hardware to a library
specific to the computer, need some tweaks. In that case, since the
modifications are specific to a lab, and only on one or a few
computers of that lab, it is appropriate to adjust the `config.cmake`
file that you find in the `.config/class/<machineclass>`
folder. Generally, the location of the header files for the IO
library, and the IO library files, are indicated there.

### Per-module CMakeLists.txt file.

Each module also has a `CMakeLists.txt` file, in the module
folder. When your module uses specific software external to your
project, such as a scene graph library, math software, or the like,
this can be indicated in that CMakeLists.txt. Also if you have for
example two or more modules that use, e.g., an xml library, please use
the per-module CMakeLists.txt. CMake itself will ensure that the
detection of software is cached, and only done once, and the libraries
you indicate are also only linked once. Placing the cmake calls here,
instead of in the main CMakeLists.txt file, ensures that the modules
can also be borrowed and re-used in other projects individually.

The comm-objects folders also have a `CMakeLists.txt` file that mostly
resembles the one in the modules. The only difference is that there is
a target added for the generated code. You can adapt this file in the
same way as for the module `CMakeLists.txt`. Note that, unless you
define super-smart dueca communication objects (that maybe use matrix
libraries or the like), adapting this file is seldom needed.

## Common approach

### Running CMake

To use cmake, you call the program cmake from any directory, and the
only required argument is the directory where you main CMakeLists.txt
file is located. For DUECA projects, we use the folder `build`, and
after entering that folder (cd build), call cmake with:

    cmake ..

It is also possible to specify variables for the build system in the
cmake call, but for DUECA projects this is normally not used. Any
specific configuration or adjustment can be done per machine class, in
the machine class `config.cmake` file.

If successful, this has now filled the build directory with files
needed for building the project. The default build system uses
Makefiles, so from this build directory, call

    make

to start compilation. In some cases, if you have adapted your code or
configuration, this might automatically re-invoke cmake to update the
build system.

### Adapting the CMakeLists.txt files

If you know which library you need for compiling your module, try to
find a CMake call to find the headers and library files. As a general
rule, CMake-specific information, which is provided through the
`find_package` call with the proper arguments, is to be
preferred. Some very general libraries have files provided in CMake's
installation, in the folder `/usr/share/cmake/Modules`.

As an example:

    (set BLA_VENDOR OpenBlas)
    find_package(BLAS)

In general, the files are well documented, often search options (as
here, for a specific BLAS variant, after setting a specific variable),
are possible.

After such an invocation, the find_package script will leave result
variables in the cmake program. By convention, there is always a
variable to indicated success (in this case `BLAS_FOUND`, and
variables that give the list of libraries, include paths (which blas
does not do), or other components.

In this case, you would add `${BLAS_LIBRARIES}` to the `LIBRARIES`
specified in the `dueca_add_module` call.

Another option, if the default CMake search is not possible, is to use
a different system, `pkg-config`, to get the information on a library.

    pkg_check_modules(TINYXML tinyxml REQUIRED)

This sets variables `TINYXML_FOUND`, `TINYXML_LIBRARIES` and
`TINYXML_INCLUDE_DIRS`. Note that here the variable prefix (here
`TINYXML`) was specified by you. The `tinyxml` corresponds to the name
of the pkg-config file.

Adding the keyword `REQUIRED` makes CMake configuration stop when the
library is not found. Again, use the produced variables
(`TINYXML_INCLUDE_DIRS` and `TINYXML_LIBRARIES`) to indicate which
header locations to search and which libraries to include, like in the
following example:

    # add current module as target, optionally add required dueca components
    dueca_add_module(
      SOURCES ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/comm-objects.h

      # specify include directories, add for used libraries as needed
      INCLUDEDIRS ${DCO_INCLUDE_DIRS} ${TINYXML_INCLUDE_DIRS}

      # optionally add more DUECA components (e.g., a graphics toolkit?)
      # DUECA_COMPONENTS

      # if you directly use code from other modules here, specify these
      # in the form of project/module. Code generation order will be correct,
      # and the source location of these modules is added to the include path
      USEMODULES

      # optionally specify libraries used for this module
      LIBRARIES ${TINYXML_LIBRARIES} ${BLAS_LIBRARIES}

      # give compiler options
      COMPILEOPTIONS ${CUSTOM_OPTIONS}
    )


In case none of that is available, there are more primitive ways to
check the location of a library or header, with `find_path` and
`find_library`.

### Folders used by the CMake build

You have seen from the snippets above, that CMake variables can be
accessed by enclosing them in curly brackets and a Dollar sign:
`${MY_VARIABLE}`. When using a `find_package` call, inspect its
documentation (or search for it on Google or the like), to see which
variables it sets.

The build system uses the `build` folder for generated code and build
results, and the project folder (e.g., `MyProject/MyProject`) as basis
for the source. In the CMake scripts, these places are defined by the
following variables:

    ${CMAKE_SOURCE_DIR}
    ${CMAKE_BINARY_DIR}

In general, it is best to keep modules independent from each other, and
have them only connect over the DCO files they communicate. However,
quite often it is useful to put helper code in modules, that is used
directly by other modules.

Thus, if you did borrow a module called `borrowed-module`, from a
project called `OtherProject`, and you need to include a header from
this folder, you should add the following to the `USEMODULES` of the
module where you need that header:

    dueca_add_module(
      SOURCES ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/comm-objects.h

      # specify include directories, add for used libraries as needed
      INCLUDEDIRS ${DCO_INCLUDE_DIRS}

      # optionally add more DUECA components (e.g., a graphics toolkit?)
      # DUECA_COMPONENTS

      # if you directly use code from other modules here, specify these
      # in the form of project/module. Code generation order will be correct,
      # and the source location of these modules is added to the include path
      USEMODULES
      OtherProject/borrowed-module

      # optionally specify libraries used for this module
      LIBRARIES

      # give compiler options
      COMPILEOPTIONS ${CUSTOM_OPTIONS}
    )

It would also be possible to add the path to the module source to the
`INCLUDEDIRS` argument, like this:

      # specify include directories, add for used libraries as needed
      INCLUDEDIRS ${DCO_INCLUDE_DIRS}
      ${CMAKE_SOURCE_DIR}/../OtherProject/borrowed-module

(As a side note, `../` walks up one folder, then `OtherProject/` enters the
OtherProject folder, etc.). However this approach may fail, because most
modules rely on generated code as well. The file `comm-objects.lst` will
be converted to a file `comm-objects.h`, that includes all communication
(DCO) objects used by a specific module. If you don't tell CMake, this
file generation might take place too late. Adding the used module to
USEMODULES, ensures both that the code generation in the used module takes
place first, and that the correct paths are set.

The two variables:

    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_BINARY_DIR}

indicate the location of the source and generated code of the current
module or comm-objects folder. As you can see in the example
`dueca_add_module` call above, the file `comm-objects.h`, which is
generated from a `comm-objects.lst` file in your source folder, ends
up in the `${CMAKE_CURRENT_BINARY_DIR}`, and since it is part of the
module, needs to be added to the sources. Note that CMake sources
include both header files (`.h`, `.hxx`) and source files (`.c`,
`.cxx`, even Fortran; `.f`, etc). CMake is smart enough to determine
which sources to compile. Adding also the headers enables CMake to
figure out when to recompile after edits in header files.

In general, avoid using absolute -- and computer dependent -- paths in
your CMake configuration; use CMake's detection mechanisms to find out
where headers and libraries are located, and for finding files in
borrowed DUECA modules use relative paths starting from the
`CMAKE[_CURRENT]_SOURCE_DIR` and `CMAKE[_CURRENT]_BINARY_DIR`.

## The DCO code generation

The `CMakeLists.txt` file in the comm-objects folders also has
instructions for the code generation. Occasionally these need to be
tweaked; when using a fixed-length array, where the length of the
array is determined from a C++ enum variable or a define variable, a
small program needs to be generated to get this array length. This
program might need to find included headers, and you can add an
`INCLUDEDIRS` variable, much like for the `dueca_add_module` call, to
indicate the location of these header files. An example:

    # specify codegen target to convert the DCO sources to C++
    # sets DCO_OUTPUTS to a list of all generated source
    duecacodegen_target(OUTPUT DCO DCOSOURCE ${ACTIVEDCO}
      INCLUDEDIRS ${CMAKE_CURRENT_SOURCE_DIR})


## Recipes for CMake use

In the following, some common recipes for finding software are discussed.

### Gtk+extra, versions 2.0 and 3.0

These two versions are mostly compatible. The following snippet finds the
latest one:

    pkg_check_modules(GTKEXTRA gtkextra-3.0)
    if(NOT GTKEXTRA_FOUND)
      pkg_check_modules(GTKEXTRA gtkextra-2.0 REQUIRED)
    endif()

Results:

| Variable                  | Description             |
| ------------------------- | ------------------------|
| `GTKEXTRA_FOUND`          | Success indicator       |
| `GTKEXTRA_LIBRARIES`      | Libraries               |
| `GTKEXTRA_INCLUDE_DIRS`   | location of the header  |

###  Platform-specific configuration

The control loading application needs to link with platform specific
IO libraries, depending on the computer where the control loading is
run, number of axes and capabilities of the control loaders
differ. This is added to the machine class `cmake.config` file for the
respective platform. Here is an example for the HMI laboratory control
loading, it uses basic `find_path` and `find_library` calls:

	# find distinctive hmi-io headers
    find_path(HMIIO_INCLUDE_DIR 
	          NAMES HardwareInterface.hxx hmiio_calib_volts.h REQUIRED 
			  PATHS $ENV{HOME}/dapps/hmi-io)
    list(APPEND HMIIO_INCLUDE_DIRS 
	     ${HMIIO_INCLUDE_DIR} ${HMIIO_INCLUDE_DIR}/build)

	# check that the library is there
    find_library(HMIIO_LIBRARY NAMES hmi-io REQUIRED 
	             PATHS $ENV{HOME}/dapps/hmi-io/build)
    
	# ethercat
	pkg_check_modules(ETHERCAT libethercat REQUIRED)

	# pass the result to the project
	set(PROJECT_LIBRARIES ${HMIIO_LIBRARY} ${ETHERCAT_LIBRARIES})
    set(PROJECT_INCLUDE_DIRS ${HMIIO_INCLUDE_DIRS} ${ETHERCAT_INCLUDE_DIRS})

Results:

| variable                | description             |
| ----------------------- | -----------             |
| `HMIIO_LIBRARY`         | Library                 |
| `HMIIO_INCLUDE_DIRS`    | location of the headers |

The code for the SIMONA Research Simulator is similar, enter it in
`.config/class/srs-io.cmake`:

    # find distinctive srs-ecat-io headers
    find_path(SRSIO_INCLUDE_DIR 
		      NAMES HardwareInterface.hxx build/srs-ecat-io-config.h REQUIRED 
			  PATHS $ENV{HOME}/apps/srs-ecat-io)
    list(APPEND SRSIO_INCLUDE_DIRS 
	     ${SRSIO_INCLUDE_DIR} ${SRSIO_INCLUDE_DIR}/build)

	# check that the library is there
    find_library(SRSIO_LIBRARY NAMES srs-ecat-io REQUIRED 
	             PATHS $ENV{HOME}/apps/srs-ecat-io/build)

	# pass the result to the project
	set(PROJECT_LIBRARIES ${SRSIO_LIBRARY})
    set(PROJECT_INCLUDE_DIRS ${SRSIO_INCLUDE_DIRS})

Results:

| variable                | description             |
| ----------------------- | -----------             |
| `SRSIO_LIBRARY`         | Library                 |
| `SRSIO_INCLUDE_DIRS`    | location of the headers |


###  Tinyxml header

Tinyxml is one of the easy and commonly used XML libraries. Version 1
and 2 are available, and unfortunately these are not compatible, so a
switch to version 2 requires adaptation to your code.

    # TINYXML 1
    pkg_check_modules(TINYXML tinyxml REQUIRED)

    # TINYXML 2
    find_package(tinyxml2)

Results:

| variable                 | description              |
| ------------------------ | ------------------------ |
| `TINYXML_LIBRARIES`      | Library                  |
| `TINYXML_INCLUDE_DIRS`   | location of the headers  |
| `TINYXML2_LIBRARIES`     | Library                  |
| `TINYXML2_INCLUDE_DIRS`  | location of the headers  |

### Pugixml

Pugixml is another commonly used XML library. It is more modern C++
than TinyXML.

    pgk_check_modules(PUGIXML pugixml REQUIRED)

Results:

| variable                 | description              |
| ------------------------ | ------------------------ |
| `PUGIXML_LIBRARIES`      | Library                  |
| `PUGIXML_INCLUDE_DIRS`   | location of the headers  |

### OpenSceneGraph

OpenSceneGraph can show 3D worlds on out of the window displays.

    find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgUtil osgViewer)

Results:

| variable                        | description              |
| ------------------------------- | ------------------------ |
| `OPENSCENEGRAPH_LIBRARIES`      | Library                  |
| `OPENSCENEGRAPH_VERSION`        | Version                  |
| `OPENSCENEGRAPH_INCLUDE_DIRS`   | location of the headers  |

### Simulink Coder projects

Simulink coder projects have a small file defines.txt, that describes
the required defines for compiling the code. The following snippet
runs through this file, and adds the defines to `CUSTOM_OPTIONS`:

    # RTW defines
    file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/defines.txt DEFINESTXT)
    foreach(L IN LISTS DEFINESTXT)
      if(NOT L STREQUAL "RT")
        list(APPEND CUSTOM_OPTIONS "-D${L}")
      endif()
    endforeach()

A small note: This skips the `RT` option (`dueca-config` will add that
one for you), since defining -DRT means defining the common letter
combination `RT` to nothing, and that breaks quite a lot of code in
deep and troubling ways. Such a define simply replaces occurrences of
`RT` in your C or C++ code. `dueca-config` keeps that sane by defining
`RT=RT`. Add the appropriate `rtwvxx_xx` to the `DUECA_COMPONENTS`
argument in your `CMakeLists.txt` file of the module with the rtw code.

### HMILibToolkit files

The hmi toolkit can support the development of instrument
displays. Interface header and source files are generated from `.if`
files, to add this generation to your CMakeLists.txt file, use the
following snippets:

    # get support code
    include(HMILibAddIf)

    # find all the if files. Note that there are often several
    # in the 'components' folder
    file(GLOB HMICOMP "components/*.if")
    set(HMILIBFILES .... ${HMICOMP})

    # generate the conversions
    hmilib_add_if(HMILIB_SOURCES HMILIBSRC HMILIB_IF ${HMILIBFILES})

Now the variable `HMILIBSRC` contains a list of the generated
sources. Add that to the `dueca_add_module` call to ensure that these
are being built.

    dueca_add_module(
      SOURCES ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/comm-objects.h
      ${HMILIBSRC}

      # specify include directories, add for used libraries as needed
      INCLUDEDIRS ${DCO_INCLUDE_DIRS}

      # to get to the hxx files that are under components, both
      # generated and in source
      ${CMAKE_CURRENT_BINARY_DIR}/components
      ${CMAKE_CURRENT_SOURCE_DIR}/components
    ...
