CMake

Aside from Gnu Autotools, one other tool that is very popular for building projects is "CMake". Just like GNU Autotools, it's open source and cross platform. It also provides support for testsing and packaging using Ctest and Cpack. Cmake project started in 2000. The executable pgms Cmake,  Cpack and Ctest are all written in C++. CMake/CPack/Ctest are case insensitive, meaning that we can write name of pgms, files or text in files in any case, and it will mean the same (for ex: CMake can be written in any case, i.e cmake, Cmake, etc, and it still runs correctly).

Links:

https://cmake.org/cmake-tutorial/

https://www.johnlamp.net/cmake-tutorial.html  => whole tutorial also avilable on pdf: https://www.johnlamp.net/files/CMakeTutorial.pdf

 https://ecrafter.wordpress.com/category/programming/

https://ilcsoft.desy.de/portal/e279/e346/infoboxContent560/CMake_Tutorial.pdf

Just as Make uses Makefile which is used to build source code, CMake has CmakeLists.txt files. These describe your project to CMake. Cmake generates Makefiles for you in Linux, instead of we writing it manually. Then we run make using these Makefiles to create executables and libraries, just as we do with regular make. These Makefiles are native build files (i.e generated based on what's available on current system), so can be run on that system (hence called cross platform)

CmakeLists.txt are fairly simple especially compared to Makefiles.Note Cmake is also inteneded for other platforms as Microsoft Windows and Apple Mac, so it is used to generate other build system other than Makefiles. We'll talk only about Unix Makefile generation using Cmake, as that is what Cmake is used for on Linux platforms.

A simple 3 line CmakeLists.txt is as follows:

CMakeLists.txt: NOTE: End of each line is signaled with newline, no ; or anything else needed to signify end of line

cmake_minimum_required(VERSION 2.8 FATAL_ERROR) #This line is optional, but is strongly recommended. This adds version number. 2.8 as version number. Last option "FATAL_ERROR" is optional. This whole line says that if version of cmake is < 2.8, then cmake would error out with FATAL ERROR. If you aren’t sure what version to set use the version of CMake you have installed (cmake -version). It is recommended that this command be used in all top level CMakeLists.txt

#This is comment. Comments can also be used at end of cmd on same line as shown in above line. Multi line comments are as #[ ... #]
project("My tutorial") => Name of project, if name has spaces, put them inside " "

add_executable(tutorial tutorial.cxx) => This command tells CMake you want to make an executable and adds it as a target. The first argument is the name of the executable and the rest are the source files. You may notice that header files aren’t listed. CMake handles dependencies automatically so headers don’t need to be listed. 

 Once we have this file in a dir, we can run cmake. We will do an out of source build, where we create all new files in a separate dir. This is a recommended approach for any build software, as it keeps generated files separate from source files. We can also do in source build where we create all new files in same dir, but in source build are not preferred.

Let's start with same example as of the tutorial in Make:

 /home/aseth/ => cp hello.c and config.h in this dir.

Create CMakeLists.txt as below, with only 2 lines:

project("My tutorial") # Name of project,if multiple words, separated by spaces, put them inside " "
add_executable(hello hello.c) #name of config.h is not put here, as CMake handles headers automatically

mkdir build
cd build/
cmake -G "Unix Makefiles" .. => This creates "Makefile" for us in same dir as "Makefile". (option -G provides Generator name, here we create Unix Makefiles as generator). It also creates couple of subdir here. ".." is needed since that specifies path of CMakeLists.txt, which is 1 level up. Here we are doing an out of source build as we are in a separate dir build. If we were doing an in source build, by running this cmd in main dir (/home/aseth), we would have to replace ".." with "." as that specifies the path of CMakeLists.txt which is now in current dir for in source build.

Build package => similar to AutoTools
make VERBOSE=1 => runs make as you would with a Makefile created manually. Makefile created above by Cmake is quite fancy. It suppresses the standard output. While this provides a neater and cleaner experience it can make debugging more difficult as you can’t check the flags passed to the compiler, etc. We can get all of that output by running make VERBOSE=1.

NOTE: in make cmd above, we didn't specify a target. If you look at Makefile generated by cmake above, you will see it has lot of std targets as all, clean, etc. By default, target "all" is run, so above cmd is equiv to "make -all VERBOSE=1"

executable hello is created in this build dir. object files for hello and other related files are all created in CMakeFiles/hello.dir subdir.

Install package => similar to AutoTools

make install => we can omit build step above, as both build and install can be handled by this cmd

CMakeCache.txt => this is one other important file created during build process, in build dir itself. It has entries of form: VAR:TYPE=VALUE. It speeds up build process. There is no need to modify it manually.

Syntax: All files in CMake are written in CMake syntax. All details of syntax can be found on CMake website. We'll just discuss important ones.

Variables: Cmake comes with list of predefined var as CMAKE_BUILD_TYPE, etc. These Variables can be changed directly in the build files (CmakeLists.txt) or through the command line by prefixing a variable's name with -D (i.e -DBUILD_SHARED_LIBS=OFF )

CMAKE_MODULE_PATH => Path (dir) to where the CMake modules are locate. These CMake modules are loaded by the the include() or find_package() commands before checking the default modules that come with CMake. By default it is empty, it is intended to be set by the project.

CMAKE_INSTALL_PREFIX => Where to put files when calling 'make install'

CMAKE_BUILD_TYPE => Type of build (Debug, Release, ...)

BUILD_SHARED_LIBS => Switch between shared and static libraries

We can change predefined var or define our own variables using set cmd. Data type for variables are strings and list (which are lists of strings). All var can be accessed via ${VAR}.

set(STRING_VARIABLE "value")

set(LIST_VARIABLE "value1 value2")


To print the value of a variable, use the message command and deference the variable using ${}:

message("The value of STRING_VARIABLE is ${STRING_VARIABLE}")

ex:

set(CMAKE_CXX_FLAGS "-Wall -std=c++0x") => this sets additional compiler flags to be applied to both compiler and linker. This var is predefined var, but we can assign any value to it.

Conditional constructs:
1. IF() ... ELSE()/ELSEIF() ... ENDIF() => ex: IF (UNIX)

2. WHILE() ... ENDWHILE()

3. FOREACH() ... ENDFOREACH()

CMake Modules: Special cmake file written for the purpose of finding a certain piece of software and to set it's libraries, include files and definitions into appropriate variables so that they can be used in the build process of another project. (e.g. FindJava.cmake, FindGSL.cmake)

 These modules are in dir where CMake is installed. On my system, cmake is installed in /usr/local/ dir, just like anyother pkg installed.
dir: /usr/local/share/cmake-3.14/Modules/ => there are hundreds of modules here as *.cmake and *.in
 
Ex: FindGSL.cmake ( /usr/local/share/cmake-3.14/Modules/ FindGSL.cmake ) => Finds native GSL includes and libraries, and sets appr var. The file reads like this:
 
include ( ...file.cmake) => includes a cmake file to handle args passed to the module
if (GSL_ROOT_DIR defined, use it),
else use PkgConfig module => find_package(PkgConfig), pkg_check_modules( GSL QUIET gsl ))
set GSL_INCLUDE_DIR, GSL_LIBRARY, GSL_CBLAS_LIBRARY, GSL_INCLUDE_DIRS, GSL_LIBRARIES, (debug versions too)
If we didn't use PkgConfig, try to find the version via gsl-config or by reading gsl_version.h. set GSL_VERSION
Now, handle the QUIETLY and REQUIRED arguments and set GSL_FOUND to TRUE if all listed variables (GSL_INCLUDE_DIR, GSL_LIBRARY, GSL_CBLAS_LIBRARY, GSL_VERSION ) are TRUE

On running, FindGSL module will set the following variables in your project (other modules may set other similar var, i.e OpenCV will set similar var, but starting with prefix OpenCV, i.e: OpenCV_FOUND, etc):

GSL_FOUND          - True if GSL found on the local system
GSL_INCLUDE_DIRS   - Location of GSL header files. => all include dir. Here, it's same as GSL_INCLUDE_DIR
GSL_LIBRARIES      - The GSL libraries. => all library, here it's both of these: GSL_LIBRARY, GSL_CBLAS_LIBRARY
GSL_VERSION        - The version of the discovered GSL install.

Packages: Packages provide dependency information to CMake based buildsystems. Let's say we installed a package for OpenCV. This package might have been built with CMake or may been built with any other pkg building software as AutoTools, etc. If package was built with CMake, then , then it has "<name>Config.cmake" (i.e OpenCVConfig.cmake) file associated  with it. This is called as Config-file package. If CMake was not used to make OpenCV, then we need to write a new file named Find<name>.cmake file (i.e FindOpenCV.cmake), and put it in the directories listed in MAKE_MODULE_PATH. This is called as Find-module package. Now, in order to use this package in another project, we have to tell our new project, how to find this package. Packages are found with the find_package() macro. Both of this kind can be found using find_package() cmd.

1. Find-module Packages => These are the ones that have Find<name>.cmake file associated. This is the default package type that is assumed for any package (i.e CMake assumes that external package was not built with CMake). In order to use YARP package in new project called "hello", CMake searches the directories listed in CMAKE_MODULE_PATH for a file called Find<name>.cmake. If found, this macro is executed and it is responsible for finding the package.

FIND_PACKAGE(YARP) => searches for file named FindYARP.cmake in dir specified by CMAKE_MODULE_PATH 

2. Config-file Packages => These are the ones that were built with Cmake and have "<name>Config.cmake" or or <lower-case-name>-config.cmake files associated. This file is created in build dir selected by user, when CMake was used to build that package. In order to use YARP package in new project called "hello", we specify the location of this file by filling a cache entry called <name>_DIR (this entry is created by CMake automatically). If the file is found, it is processed to load the settings of the package (an error or a warning is displayed otherwise).

So, we set YARP_DIR to appr dir where this file is, and then use FIND_PACKAGE macro.

SET(YARP_DIR "$ENV{YARP_ROOT}") #We can also specify DIR directly as \usr\include instead of having env var
FIND_PACKAGE(YARP) => searches for file named YARPConfig.cmake or yarp-config.cmake in YARP_DIR specified above.

YARPConfig.cmake creates the entries YARP_LIBRARIES, YARP_INCLUDE_DIRS and YARP_DEFINES. To use these in a project, do:

  INCLUDE_DIRECTORIES(${YARP_INCLUDE_DIRS})
  ADD_DEFINITIONS(${YARP_DEFINES}) # optional
  ...
  TARGET_LINK_LIBRARIES(your_target_name ${YARP_LIBRARIES})

include:  include(file|module) => Load and run CMake code from a file or module. If a module is specified instead of a file, the file with name <modulename>.cmake is searched first in CMAKE_MODULE_PATH, then in the CMake module directory. 

ex: include(FindGSL) => searches for FindGSL.cmake and runs it.

 

 

1. find_module(Qt4 MODULE) =>

1. pkg_check_modules(<PREFIX> <MODULE>) invokes pkg-config, queries for <MODULE> and returns the result in variables which have names beginning with <PREFIX>_. Thus module is the name of the software you or pkg-config are looking for.

With "pkg-config --list-all"  (type this in build dir on linux shell cmd line) you get a list of all modules known by pkg-config. If your <MODULE> is not listed here, then  pkg_check_modules can never find it. If it does find it, use "pkg-config --modversion <MODULE>" to find what version of <MODULE> it's finding.

Ctest:

/usr/local/------

Now, to enable testing, we can add more lines in CMakeLists.txt :

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project("My tutorial") 
enable_testing() #Enables testing for this CMake project. This should only be used in top level CMakeLists.txt. The main thing this does is enable the add_test() command.  
add_executable(hello hello.c)
add_test(HelloTest hello) => The enable_testing() function we added to our CMakeLists.txt adds the “HelloTest” target to our Makefile. Making the “HelloTest” target will run CTest which will, in turn, run all of our tests. In our case just the one.

 CPack:

-------