cmakedefinitioncmake-cache

Easier/terser mechanism for translating CMake option into preprocessor definitions?


I find myself repeating the following segment of code in my CMakeLists.txt:

option(SOME_OPT "Some option" ON)
if (SOME_OPT)
    target_compile_definitions(my_app SOME_IDENTIFIER_HERE_RELATED_TO_OPT)
endif()

(and occasionally something similar but with a string value for the -D).

Is there a mechanism in CMake (never mind the version) which makes this easier, or more terse?


Solution

  • Option 1: Using generator expressions

    Here's one option using target properties and generator expressions:

    option(SOME_OPT1 "Some option 1" ON)
    option(SOME_OPT2 "Some option 2" OFF)
    option(SOME_OPT3 "Some option 3" ON)
    
    target_compile_definitions(my_app PRIVATE
      $<$<BOOL:${SOME_OPT1}>:SOME_IDENTIFIER_HERE_RELATED_TO_OPT1>
      $<$<BOOL:${SOME_OPT2}>:SOME_IDENTIFIER_HERE_RELATED_TO_OPT2>
      $<$<BOOL:${SOME_OPT3}>:SOME_IDENTIFIER_HERE_RELATED_TO_OPT3>
    )
    

    The $<BOOL:${var}> dance is necessary to translate truthy/falsey CMake values to either 1 or 0, as the generator expression mini-language expects.

    Option 2: A generated "configure file"

    Another option if you have many of these is to use the configure_file command to create a header with the relevant information. Create a header called config.h.in (as an example) with the following contents:

    #ifndef MYPROJ_CONFIG_H
    #define MYPROJ_CONFIG_H
    
    #cmakedefine SOME_IDENTIFIER_HERE_RELATED_TO_OPT1
    #cmakedefine SOME_IDENTIFIER_HERE_RELATED_TO_OPT2
    #cmakedefine01 SOME_IDENTIFIER_HERE_RELATED_TO_OPT3
    
    #endif
    

    The #cmakedefine VAR directive is processed by configure_file to set VAR if VAR is defined in the CMakeLists.txt as something truthy and to comment out the line if not. The 01 variant always defines VAR, but sets it to either 0 or 1.

    Then in your CMakeLists.txt you could write:

    option(SOME_OPT1 "Some option 1" ON)
    option(SOME_OPT2 "Some option 2" OFF)
    option(SOME_OPT3 "Some option 3" ON)
    
    # Setting these variables exposes them to configure_file below
    set(SOME_IDENTIFIER_HERE_RELATED_TO_OPT1 "${SOME_OPT1}")
    set(SOME_IDENTIFIER_HERE_RELATED_TO_OPT2 "${SOME_OPT2}")
    set(SOME_IDENTIFIER_HERE_RELATED_TO_OPT3 "${SOME_OPT3}")
    
    configure_file(config.h.in config.h)
    
    target_include_directories(my_app PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>")
    

    Your source files could then #include <config.h>, which would be, by default:

    #ifndef MYPROJ_CONFIG_H
    #define MYPROJ_CONFIG_H
    
    #define SOME_IDENTIFIER_HERE_RELATED_TO_OPT1
    /* #undef SOME_IDENTIFIER_HERE_RELATED_TO_OPT2 */
    #define SOME_IDENTIFIER_HERE_RELATED_TO_OPT3 1
    
    #endif
    

    Both options cost one line of overhead per option, though the first approach is a little noisy and the second approach costs a file. However, the second approach also lets you add additional preprocessor code that might be better placed together in the configured file. It can also substitute CMake variable values via the @VAR@ syntax. See the documentation for more detail: https://cmake.org/cmake/help/latest/command/configure_file.html