Search code examples
cmakecmakelists-optionscmake-language

CMake: what is the correct syntax for $<CONFIG:cfg_list> or $<IN_LIST:str,list>?


Using CMake version 3.20.1, I'm doing something that I consider to be relatively simple: verify that the value of cfg pass via -DCMAKE_BUILD_TYPE=<cfg> is a valid configuration.

Referencing the CMake generator expressions docs, it appears that $<CONFIG:cfgs> is a specialized case of $<IN_LIST:string,list> where the string is ${CMAKE_CONFIGURATION_TYPES}. The CMake runtime-error I'm getting reinforces this, as well as telling me that I'm missing something.

What I'm expecting to work is this:

    set( CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Build Configurations" FORCE )
    
    if( NOT $<CONFIG:${CMAKE_CONFIGURATION_TYPES}> )
      # Handle_the_bad_value()
    endif()

when called from the command line as cmake .. -DCMAKE_BUILD_TYPE=Debug, but I get the following error:

CMake Error at CMakeLists.txt:45 (if):
  if given arguments:

    "NOT" "\$<CONFIG:Debug" "Release>"

  Unknown arguments specified    

Replacing

$<CONFIG:<cfg_list>

with either:

$<IN_LIST:${CMAKE_BUILD_TYPE},${CMAKE_CONFIGURATION_TYPES}>

or

$<IN_LIST:"Debug","Debug;Release">

gives me the following:

CMake Error at CMakeLists.txt:43 (if):
  if given arguments:

    "NOT" "\$<IN_LIST:Debug,Debug" "Release>"

  Unknown arguments specified

I've played around with various changes to the original syntax before experimenting with $<IN_LIST:str,list>, but being that I cannot get the IN_LIST syntax to work, I feel there is something fundamental that I am getting wrong that I'm also failing to grasp from the documentation.


Subsequent experimentation, using:

    if( NOT ${CMAKE_BUILD_TYPE} IN_LIST ${CMAKE_CONFIGURATION_TYPES} )

yields:

CMake Error at CMakeLists.txt:43 (if):
  if given arguments:

    "NOT" "Debug" "IN_LIST" "Debug" "Release"

  Unknown arguments specified

So I tried brute force:

if( NOT ( "Debug" IN_LIST "Debug;Release" ) )

which evaluates, but incorrectly, because it does not find "Debug" in the list, so it enters the code within the if-statement. Final try:

    if( NOT ( CMAKE_BUILD_TYPE IN_LIST CMAKE_CONFIGURATION_TYPES ) )

Does work as expected. Noted: NOT should apply to an expression that is within parenthesis. But it still doesn't help me understand the correct syntax for the CONFIG generator expression.


Solution

  • You cannot use generator expressions in if command, because generator expressions are expanded only at the end of configuration process, but if condition needs to be evaluated immediately.

    Expression $<IN_LIST:<$CONFIG>,${CMAKE_CONFIGURATION_TYPES}> is valid in BOOL context of other generator expression. But it could be used only for produce another string, not for emit an error.

    If you want to check correctness of the build type, specified by a user, following things should be taken into account:

    1. On multi-configuration generators (like Visual Studio) a build type is given by --config option at build stage. CMake automatically checks such build type for match against CMAKE_CONFIGURATION_TYPES, so there is no needs in manual checks.

    2. On single-configiration generators (like Makefile) a build type is specified via CMAKE_BUILD_TYPE variable. So there is no needs in a generator expression to refer a build type.

    3. Variable CMAKE_CONFIGURATION_TYPES should be specified only for multi-configuration generators. Setting it for single-configuration generators could confuse some scripts (e.g. FindXXX.cmake).

    So, you could check a build type in the following way (based on the code in that answer)

    # Use custom variable for avoid setting CMAKE_CONFIGURATION_TYPES
    # for single-configuration generators.
    set(MY_CONFIGURATION_TYPES "Debug;Release")
    
    # Check whether generator is single-configuration or multi-configuration
    get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
    if(isMultiConfig)
        # Multi-configuration generator
        # Set possible values. CMake automatically checks correctness.
        set(CMAKE_CONFIGURATION_TYPES "${MY_CONFIGURATION_TYPES}" CACHE STRING "" FORCE) 
    else()
        # Single-configuration generator
        if(NOT CMAKE_BUILD_TYPE)
            # If not set, set the build type to default.
            message("Defaulting to release build.")
            set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
        else()
            # Check correctness of the build type entered by user
            # Note that the right operand for IN_LIST should be **variable**,
            # not just a list of values
            if (NOT (CMAKE_BUILD_TYPE IN_LIST MY_CONFIGURATION_TYPES))
                message(FATAL_ERROR "Incorrect build type")
            endif()
        endif()
        set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
        # set the suggested values for cmake-gui drop-down list
        # Note, that CMake allows a user to input other values.
        set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${MY_CONFIGURATION_TYPES}")
    endif()