Using configuration options in c++ using CMake

Sometimes when writing generic code you want to only using certain bits of code in special circumstances, for example only use OpenCV code when it is availble. With CMake this is easy.

CMake hello world!

Here is our hello-world.cc code

#include <iostream>

int main()
{
  std::cout << "hello world" << std::endl;
  return 0;
}

and simple CMakeLists.txt

project( hello )
set (hello_VERSION_MAJOR 1)
set (hello_VERSION_MINOR 0)
add_executable( hello hello-world.cc )

config.h

The fist step is to generate a config.h file. This file can be automatically be generated by CMake and then included into your c++ code. As a simple example, this is an example of config.h.in which CMake will convert into config.h for us:

// the configured options and settings for hello
#define hello_VERSION_MAJOR @hello_VERSION_MAJOR@
#define hello_VERSION_MINOR @hello_VERSION_MINOR@

To tell CMake to generate the config.h for us, we update our CMakeLists.txt file:

# configure a header file to pass some of the CMake settings
# to the source code
project( hello )
set (hello_VERSION_MAJOR 1)
set (hello_VERSION_MINOR 0)

# tell CMake to generate config.h
configure_file (
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_BINARY_DIR}/config.h"
  )

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

add_executable( hello hello-world.cc )

Note that we save the config.h.in file in our source directory and the generated config.h is in the build directory. This means that this file is set a configure time and several out-of-source builds can have different config.h files. We must also include the project build directory so that the new file can be included.

We can update hello-world.cc to make use of our new configuration details:

#include "config.h"
#include <iostream>

int main()
{
  std::cout << "hello world" << std::endl;
  std::cout << "hello version " << hello_VERSION_MAJOR << "."
      << hello_VERSION_MINOR << std::endl;
  return 0;
}

The result of calling cmake is a file in the build directory called config.h containing:

#define hello_VERSION_MAJOR 1
#define hello_VERSION_MINOR 0

Compiling and running the code gives:

$ ./hello
hello world
hello version 1.0

#cmakedefine

Often CMake is used in order to test whether various libraries exist on the system using the find_package mechanism. We can take advantage of this feature and compiler directives in order to change which parts of our code are compiled. This might be useful for example to only use code which depends on a library if that library is found!

In our simple example, we will simply use two variables YES and NO which will be set to true or false.

# configure a header file to pass some of the CMake settings
# to the source code
project( hello )
set (hello_VERSION_MAJOR 1)
set (hello_VERSION_MINOR 0)

set (YES True)
set (NO False)

# tell CMake to generate config.h
configure_file (
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_BINARY_DIR}/config.h"
  )

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

add_executable( hello hello-world.cc )

This is incorporated into the config.h.in file using the #cmakedefine directive

#define hello_VERSION_MAJOR @hello_VERSION_MAJOR@
#define hello_VERSION_MINOR @hello_VERSION_MINOR@

#cmakedefine YES
#cmakedefine NO

Calling cmake results in an update config.h file:

#define hello_VERSION_MAJOR 1
#define hello_VERSION_MINOR 0

#define YES
/* #undef NO */

cmake has substituted the directive with either a #define directive or a commented out #undef directive.

This can be used in our hello-world.cc code as:

#include "config.h"
#include <iostream>

int main()
{
  std::cout << "hello world" << std::endl;
  std::cout << "hello version " << hello_VERSION_MAJOR << "."
      << hello_VERSION_MINOR << std::endl;

#ifdef YES
  std::cout << "YES is true" << std::endl;
#endif
#ifdef NO
  std::cout << "NO is true" << std::endl;
#endif

  return 0;
}

Compiling and running the code gives:

$ ./hello
hello world
hello version 1.0
YES is true

See also

This post is based on this Cmake tutorial which gives lots of other options of how to build a clever CMakeLists.txt too.