How to handle more than 10 config flags in if statement?

Hi all,

In my project, an execution step is controlled by about 10 ~ 15 config flags. Currently I implement a piece of ugly code as shown below:

 if (CONFIG_A OR
     CONFIG_B OR
     CONFIG_C OR
     CONFIG_D OR
     ...
     CONFIG_X)
         some code here
 endif()

Can I ask for your suggestion to improve the code above? Any “Modern CMake” style to handle this case?
Any suggestion is welcome!

Best regards
David Hu

Not really. If your configuration flags depend on each other, CMakeDependentOption might be able to help wrangle them at “declaration” time at least (usually works better for AND chains though). But if it really is “any of these n things”, there’s not much available AFAIK.

Thanks Ben!

Those config flags share the same prefix, for example CONFIG_TEST_XXX.
Can CMake support to check if any config flag with prefix CONFIG_TEST_ is ON?
If it can be implemented, perhaps it can help shorten the long if () statement.

No, CMake has no such metaprogramming facilities. Possibly this if you can stomach it:

set(options A B C)
set(conditional)
foreach (option IN LISTS options)
  list(APPEND conditional "COMMON_PREFIX_${option}")
endforeach ()
# (ab)use the list representation
string(REPLACE ";" ";OR;" conditional "${conditional}")
if (${conditional})
  # code that should work for any `option` being true
endif ()

Thanks for the suggestion.

Perhaps if () looks more straightforward. :stuck_out_tongue:
I will try to live with the long if ().

@tbacarrot I have another suggestion/idea: Use a function + cmake_language(CALL) to handle the config variants and their implementation separately. Could look something like this:

function(my_config_variant)
  # args
  cmake_parse_arguments(_ARG "" "PREFIX;CALL" "WHEN;CALL_ARGS" ${ARGN})
  if(_ARG_PREFIX)
    string(REGEX REPLACE "[A-Za-z0-9_/.+-]+" "${_ARG_PREFIX}\\0" _ARG_WHEN "${_ARG_WHEN}")
  endif()
  # some regex magic to transform the WHEN argument to a valid if condition
  string(REGEX REPLACE "\\(;" "(" _ARG_WHEN "${_ARG_WHEN}")
  string(REGEX REPLACE ";\\)" ")" _ARG_WHEN "${_ARG_WHEN}")
  string(REGEX REPLACE "!;?" "NOT;" _ARG_WHEN "${_ARG_WHEN}")
  string(REPLACE "&&" "AND" _ARG_WHEN "${_ARG_WHEN}")
  string(REPLACE "||" "OR" _ARG_WHEN "${_ARG_WHEN}")
  # check condition and call implementing function/macro
  if(${_ARG_WHEN})
    cmake_language(CALL ${_ARG_CALL} ${_ARG_CALL_ARGS})
  endif()
endfunction()

# config options
option(MY_CONF_A "" ON)
option(MY_CONF_B "" OFF)
option(MY_CONF_C "" ON)

# config variants implementation
function(my_conf_1_impl)
  # my_conf_1 implementation ...
endfunction()
function(my_conf_2_impl)
  # my_conf_2 implementation ...
endfunction()
# ...

# config variants
my_config_variant(
  PREFIX MY_CONF_
  WHEN A && B && C # -> false
  CALL my_conf_1_impl
)
my_config_variant(
  PREFIX MY_CONF_
  WHEN (A && B) || C # -> true
  CALL my_conf_2_impl
)
my_config_variant(
  PREFIX MY_CONF_
  WHEN A || C # -> true
  CALL my_conf_3_impl
)
my_config_variant(
  PREFIX MY_CONF_
  WHEN !B # -> true
  CALL my_conf_4_impl
)

Depending on your use case (how many/complex config variants you have) this may help to express your config variants in a more concise way than some mile-long if-elseif-else construct. Also you could bundle all the conditions for your variants in one place whereas with if statements you have to search for them at the beginning of every branch with all the implementation in between.

Though, for a small number of config variants I would stick with straightforward if statements.

Thanks @petwu .

Sorry for picking this up again.
I currently implement a search of those configs to see if any of them is set. The basic idea is:

  • Those flags are passed in via cmake -D in build command
  • Those flags are all prefixed with (assumed) ABC_

So I search those configs with ABC_ prefix in the cache like the code below

get_cmake_property(CACHE_VARS CACHE_VARIABLES)

foreach(CACHE_VAR ${CACHE_VARS})
    string(REGEX MATCH "^ABC.*" _FOUND "${CACHE_VAR}")
    if (_FOUND AND "${${CACHE_VAR}}")
        set(ABC_FLAG ON)
        break()
    endif()
endforeach()

It works well. But my friend told me that CACHE_VARIABLES is “intended for debugging purpose”, according to https://cmake.org/cmake/help/v3.21/prop_dir/CACHE_VARIABLES.html.

Is it proper to use CACHE_VARIABLES in cmake code, not for debug purpose only? Is there any risk that CACHE_VARIABLES may be deprecated later?

CACHE_VARIABLES isn’t likely to be deprecated, but someone doing a misspelling of -DABC_SOME_WRONG_FLAG=ON will be caught up here. It is intended for debugging and using it for logic just has unintended consequences. I think an explicit list is just better.

You also shouldn’t assume that the variable will be a cache variable. Projects may set them as regular non-cache variables and that should work too (not saying that’s always a thing you want to support, but in general, code shouldn’t care whether a variable is set as cache or non-cache unless there is a very specific reason to differentiate between the two).