CMake NOT operator broken?

I found a case where the CMake NOT operator doesn’t behave as I expected. I expect that if I negate the condition by prefixing NOT to it then the other branch of the if-statement is taken, but that doesn’t always happen. My CMake code is as follows:

include(CheckSymbolExists)

# cmath contains sin but not nosin
check_symbol_exists(sin "cmath" HAVE_SIN)
check_symbol_exists(nosin "cmath" HAVE_NOSIN)

message("HAVE_SIN: ${HAVE_SIN}")
message("HAVE_NOSIN: ${HAVE_NOSIN}")

if (${HAVE_SIN})
  message("sin true")
else()
  message("sin false")
endif()
if (NOT ${HAVE_SIN})
  message("not sin true")
else()
  message("not sin false")
endif()

if (${HAVE_NOSIN})
  message("nosin true")
else()
  message("nosin false")
endif()
if (NOT ${HAVE_NOSIN})
  message("not nosin true")
else()
  message("not nosin false")
endif()

My expectation was that I’d get “sin true”, “not sin false”, “nosin false”, and “not nosin true”. I got (with CMake 3.30.2 on Microsoft Windows 11)

-- Looking for sin
-- Looking for sin - found
-- Looking for nosin
-- Looking for nosin - not found
HAVE_SIN: 1
HAVE_NOSIN:
sin true
not sin false
nosin false
not nosin false

So for “nosin” the same branch of the if statement is taken regardless of the use of NOT. Is this as designed? Or is it a bug?

I need some action taken if the sought symbol is not yet available. I thought I could do that using “if (NOT ${HAVE_NOSIN}) do_action endif()” but that doesn’t work. The workaround is to use “if (${HAVE_NOSIN}) else() do_action endif()”.

Best regards,
Louis Strous

The crux of it comes down to this:

if(NOT ${some_undefined_variable})
  ...
endif()

The if() command is a bit special in how it handles its arguments. There’s different behavior depending on whether arguments are quoted or not, and there are policy settings that influence the way arguments are interpreted. For unquoted arguments, if the argument evaluates to an empty string after variable expansion has occurred, it will be as though the argument wasn’t even there. In other words, for the case above, the result will be as though you had written if(NOT). Like you, I was expecting this to result in the expression being treated as true, since if() is treated as false, but indeed if(NOT) also evaluates to false. @brad.king this would be very long-standing behavior, but it does seem inconsistent. Is this something we should consider changing (with a policy, of course)?

For the specific case you gave, you can avoid this problem by using the variable names without the ${}. This takes advantage of how the if() command treats arguments as both variable names or strings in some contexts. I’ll leave you to read up on this in the if() command docs, but your example could be expressed like so:

if (HAVE_SIN)
  message("sin true")
else()
  message("sin false")
endif()
if (NOT HAVE_SIN)
  message("not sin true")
else()
  message("not sin false")
endif()

if (HAVE_NOSIN)
  message("nosin true")
else()
  message("nosin false")
endif()
if (NOT HAVE_NOSIN)
  message("not nosin true")
else()
  message("not nosin false")
endif()
1 Like