DUECA/DUSIME
|
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.
A new script was created, dueca-gproject
, that differs from the old script in a number of ways:
.config
. This folder contains: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.machinemapping.xml
, which defines which dueca node (computer in a lab or equivalent), runs which kind of machine.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.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.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.
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.
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.
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.
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.
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.
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
.
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 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})
In the following, some common recipes for finding software are discussed.
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 |
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 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 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 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 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.
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 ...