Cmake ARGV and Macro BUG? - cmake

The following is strange!
macro(foo in1)
message("FIRST" ${in1})
message("OPtional1:" ${ARGV1})
message("OPtional2:" ${ARGV2})
if( NOT ${ARGV1} AND ${ARGV2} )
message("IF")
else()
message("ELSE")
endif()
endmacro()
Running the following:
foo("gaga" false true)
(SHOULD GIVE "IF")
WORKS!
BUT
foo("gaga" false)
(SHOULD GIVE "ELSE" because the second optional argument is FALSE!)
Results in error:
cmake:126 (if):
given arguments: "NOT" "false" "AND"
Unknown arguments specified
Is this a bug??
The following works:
if( NOT "${ARGV1}" AND "${ARGV2}" )
message("IF")
else()
message("ELSE")
endif()
WHY???
THANKS For any help!
(With functions it works)

In macros, ARGV1 etc. are not real CMake variables. Instead, ${ARGV1} is the string replacement from the macro call. That means that your call with only one argument expands to
if( NOT false AND )
which explains the error message. By enclosing the string expansions in quotes, you now have
if( NOT "false" AND "")
giving the IF command an empty string to evaluate. Other solutions: use the ${ARGC} string replacement to decide whether the argument is present before trying to expand ARGV2. Or, as you've mentioned, switch to a function instead of a macro. In a function, ARGV1 is an actual CMake variable, and you can write it more sensibly, even without the ${} expansion at all:
function(foo in1)
message("Optional1: " ${ARGV1})
message("Optional2: " ${ARGV2})
if( NOT ARGV1 AND ARGV2 )
message("IF")
else()
message("ELSE")
endif()
endfunction()
Reference:
http://www.cmake.org/cmake/help/v2.8.8/cmake.html#command:macro

if(NOT ARGV1 AND ARGV2)
is the same as
if(ARGV2 AND NOT ARGV1)
Logical negation (and most other unary operators) binds before conjunction.

Related

generator expression in add_custom_target

I'm trying to create a cmake (3.22) function that creates a target called cppclean for the target that I provide in the arguments
function (cppclean target)
if(${STATIC_CODE_ANALYSIS})
find_program(CPP_CLEAN cppclean)
if(CPP_CLEAN)
add_custom_target(cppclean
COMMAND ${CPP_CLEAN} "--include-path $<JOIN:$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>, --include-path >" $<TARGET_PROPERTY:${target},SOURCE_DIR>
VERBATIM
COMMAND_EXPAND_LISTS
)
else()
message("Cannot find cppclean")
endif()
endif()
endfunction()
However I doesn't work with the error:
No such file or directory: '--include-path ... --include-path ...
--include-path ...'
When I look at the make file that is created I indeed see the quotes around the expanded generator expression which is probably wrong.
If I remove the quotes around the generator expression it gives a different error:
cannot create /home/foo/src: Is a directory
And the make file shows that the JOIN expression is not expanded.
How to fix this?

Building opencv_contrib on Windows: "CMake Error at cmake/OpenCVDetectPython.cmake:78"

It's my fist time using cmake-gui to make the source code of opencv_contrib. And I get an error below. I've searched for it for many times,but do not get any useful help. Even I don't know what the error means. I'm not sure whether merely copying some lines of the error to Google to search is right. I'm sincerely hoping your help.
The error is :
CMake Error at cmake/OpenCVDetectPython.cmake:78 (if):
if given arguments:
"NOT" "optimized" "C:/Program Files/Python35/libs/python35.lib" "debug" "C:/Program Files/Python35/libs/python35_d.lib" "EQUAL" ""
Unknown arguments specified
Call Stack (most recent call first):
cmake/OpenCVDetectPython.cmake:219 (find_python)
CMakeLists.txt:562 (include)
The cmake/OpenCVDetectPython.cmake:73-80 is
if(_found)
set(_version_major_minor "${_version_major}.${_version_minor}")
if(NOT ANDROID AND NOT APPLE_FRAMEWORK)
ocv_check_environment_variables(${library_env} ${include_dir_env})
if(NOT ${${library_env}} EQUAL "")
set(PYTHON_LIBRARY "${${library_env}}")
endif()
note:
I get the source code (cmake/OpenCVDetectPython.cmake:73-80) from my own file. And the error comes when I try to click the generate button.
My environment is :
CPU : Intel Pentium 2020E(64X)
OS : Windows10(64X)
opencv_version : 3.1.0
cmake_version : 3.7.2(win-64X)
python_version : 3.5
VisualStudio_version : 2015
Drives me mad.....
It seems that code in cmake/OpenCVDetectPython.cmake is incorrect. (It isn't corrected in the repo too).
Proper way for check variable's non-emptiness:
if(NOT "${VAR}" STREQUAL "")
In your case original lines in cmake/OpenCVDetectPython.cmake:
if(NOT ${${library_env}} EQUAL "")
set(PYTHON_LIBRARY "${${library_env}}")
should be rewritten as:
if(NOT "${${library_env}}" STREQUAL "")
set(PYTHON_LIBRARY ${${library_env}})
Explanations are below.
The line, to which error message refers
if(NOT ${${library_env}} EQUAL "")
is a part of function's definition find_python(), and library_env is the parameter of this function.
The function is called twice: one for Python2 and one for Python3. According to error message, it is second call which fails, and it passes PYTHON3_LIBRARY as library_env argument.
So errorneous line can be read as:
if(NOT ${PYTHON3_LIBRARY} EQUAL "")
It tries to check, whether variable is not empty ... but does this wrong:
If the variable is actually empty, CMake completely omits its dereference, so the line would be read as
if(NOT EQUAL "")
which is incorrect call to if() command.
In you case, content of the variable PYTHON3_LIBRARY is a list:
optimized "C:/Program Files/Python35/libs/python35.lib" debug "C:/Program Files/Python35/libs/python35_d.lib"
It is valid value for a library, as long as it linked using target_link_libraries command.
But again, in case of list, the if() line becomes incorrect. That is why you get the error message.
Finally, EQUAL compares integers, but strings are compared with STREQUAL.
Both 1 and 2 problems can be fixed by adding qoutes around variable's dereference.
As for assignment
set(PYTHON_LIBRARY "${${library_env}}")
it incorrectly process list variables, which is exactly you case. List variables should be referenced without quotes around them:
set(A_list ${B_list})

How to specify the variable which must be set and exist in cmake

I have a need to have some variable to be specified and exist in the environment.
In case it does not exist need to stop building.
example
if ( "${VARMUSTEXIST}" STREQUAL "ON" )
message(STATUS is ON)
elif ("${VARMUSTEXIST}" STREQUAL "OFF")
message(STATUS is OFF)
endif()
I don't want to put an if (defined VARMUSTEXIST) everywhere in the script.
In bash there is an option for that "set -u".
Some preliminary points:
if ( "${VARMUSTEXIST}" STREQUAL "ON" ) [...] elif(AGAIN LONG EXPRESSION) [...] endif()normally in cmake is simply: if (VARMUSTEXIST) [...] else() [...] endif()
The command if (DEFINED VARMUSTEXIST) requires DEFINED to be upper case.
You mention bash and environment variables:Environment variables are read using $ENV{VARIABLE_NAME}
For environment variables you will do:
if(NOT DEFINED ENV{VARMUSTEXIST})
message(FATAL_ERROR "You must set VARMUSTEXIST environment variable")
endif()
You say:
I don't want to put an if (defined VARMUSTEXIST) everywhere in the script
This is not clear to me: for each variable you need to check only once, possibly in the main CMakeLists.txt. Of course, you need to add NOT: if (NOT DEFINED VARMUSTEXIST) [stop]
If you can be more precise on your problem, we can design a macro that checks if one or a group of variables are defined or not.
If by environment you mean OS environment variables then the syntax is wrong anyway.
If those are options to be provided by the user, then the literal comparisons with ON and OFF are incorrect, since CMake has more ways to express booleans and they are all in widespread use and became idiomatic. Thus, by expecting either ON or OFF, you're making your build script weird and against everyone's expectations. And you're also making more work for yourself, as you've noticed.
First, you should document the options and give them safe default values using option(). Then it won't ever be that a variable could be undefined.
# early in the top-level CMakeLists.txt
option(VARMUSTEXIST "You can provide it. It exists anyway." NO)
And then you'll check its truth or falsehood rather simply:
# later in top-level file or in subdirectories
if (VARMUSTEXIST)
message("VARMUSTEXIST is true")
else()
message("VARMUSTEXIST is false")
endif()
I think this is the best approach...
CMake Variable:
if(NOT DEFINED VARIABLE)
message(FATAL_ERROR "VARIABLE is not set")
endif(NOT DEFINED VARIABLE)
Environment Variable:
if(NOT DEFINED ENV{VARIABLE})
message(FATAL_ERROR "VARIABLE is not set")
endif(NOT DEFINED ENV{VARIABLE})

Function vs. Macro in CMake

The official document of CMake 2.8.12 says about macro
When it is invoked, the commands recorded in the macro are first
modified by replacing formal parameters (${arg1}) with the arguments
passed, and then invoked as normal commands.
and about function
When it is invoked, the commands recorded in the function are first
modified by replacing formal parameters (${arg1}) with the arguments
passed, and then invoked as normal commands.
Obviously, the two quotes are almost the same but it's confusing. Does parameter replacement behave the same in functions and macros?
I wrote a sample code below:
set(var "ABC")
macro(Moo arg)
message("arg = ${arg}")
set(arg "abc")
message("# After change the value of arg.")
message("arg = ${arg}")
endmacro()
message("=== Call macro ===")
Moo(${var})
function(Foo arg)
message("arg = ${arg}")
set(arg "abc")
message("# After change the value of arg.")
message("arg = ${arg}")
endfunction()
message("=== Call function ===")
Foo(${var})
and the output is:
=== Call macro ===
arg = ABC
# After change the value of arg.
arg = ABC
=== Call function ===
arg = ABC
# After change the value of arg.
arg = abc
So it seems arg is assigned the value of var when calling Foo and ${arg} is just string replaced with ${var} when calling Moo.
So I think the above two quotes are very easy to make one confused, although the official documents also said that:
Note that the parameters to a macro and values such as ARGN are not variables in the usual CMake sense. They are string replacements much like the C preprocessor would do
with a macro. If you want true CMake variables and/or better CMake
scope control you should look at the function command.
UPDATE (1/29/2021)
Add the following statement after the statement Moo(${var}) to make the difference between macro and function even more clear.
message(${arg})
This statement will print out abc.
In other words, function pushes and pops new variable scope (variables created and changed exist only in the function), macro does not. However, you can override the function default behaviour with the PARENT_SCOPE parameter of the set command.
The cmake documentation you quoted is so misleading that it's basically wrong. It should be clarified/fixed like this:
macro: when it is invoked, the commands recorded in the macro are first all modified before any is run by replacing formal parameters (${arg1}) with the arguments passed.
cmake --trace-expand shows exactly what happens.
The cmake 3.13.3 doc hasn't changed compared to 2.8.12 with respect to this.
The macro expansion, answered by Yantao Xie really opens my eyes!
I also found the tutorial below comes with some concrete examples, which is helpful to understand the variable scope concept.
Cited from Learn cmake in 15 mins:
In CMake, you can use a pair of function/endfunction commands to define a function. Here’s one that doubles the numeric value of its argument, then prints the result:
function(doubleIt VALUE)
math(EXPR RESULT "${VALUE} * 2")
message("${RESULT}")
endfunction()
doubleIt("4") # Prints: 8
Functions run in their own scope. None of the variables defined in a function pollute the caller’s scope. If you want to return a value, you can pass the name of a variable to your function, then call the set command with the special argument PARENT_SCOPE:
function(doubleIt VARNAME VALUE)
math(EXPR RESULT "${VALUE} * 2")
set(${VARNAME} "${RESULT}" PARENT_SCOPE) # Set the named variable in caller's scope
endfunction()
doubleIt(RESULT "4") # Tell the function to set the variable named RESULT
message("${RESULT}") # Prints: 8
Similarly, a pair of macro/endmacro commands defines a macro. Unlike functions, macros run in the same scope as their caller. Therefore, all variables defined inside a macro are set in the caller’s scope. We can replace the previous function with the following:
macro(doubleIt VARNAME VALUE)
math(EXPR ${VARNAME} "${VALUE} * 2") # Set the named variable in caller's scope
endmacro()
doubleIt(RESULT "4") # Tell the macro to set the variable named RESULT
message("${RESULT}") # Prints: 8
Both functions and macros accept an arbitrary number of arguments. Unnamed arguments are exposed to the function as a list, through a special variable named ARGN.
Here’s a function that doubles every argument it receives, printing each one on a separate line:
function(doubleEach)
foreach(ARG ${ARGN}) # Iterate over each argument
math(EXPR N "${ARG} * 2") # Double ARG's numeric value; store result in N
message("${N}") # Print N
endforeach()
endfunction()
doubleEach(5 6 7 8) # Prints 10, 12, 14, 16 on separate lines
Another notable difference between function() and macro() is the behavior of return().
From the cmake documentation of return():
Note that a macro, unlike a function, is expanded in place and therefore cannot handle return().
So because it is expanded in place, in a macro() it returns from the caller. While in a function it just exits the function()
Example:
macro(my_macro)
return()
endmacro()
function(my_function)
return()
endfunction()
my_function()
message(hello) # is printed
my_macro()
message(hi) # is not printed

CMake compare to empty string with STREQUAL failed

I always think that if you want to compare two strings (but not variables) all you need to do is to quote it like that:
if("${A}" STREQUAL "some string")
but now I find out that this code sometimes print oops:
cmake_minimum_required(VERSION 2.8)
if("d" STREQUAL "")
message("oops...")
endif()
May be it's bug (because it prints with Xcode, but not with make)?
Or there is some special variables?
cmake: 2.8.12, 2.8.11.2
xcode: 4.6.2, 5.0.1
Update
There is command string without described problems:
string(COMPARE EQUAL "${A}" "" result)
if(result)
message("...")
endif()
Update 2
The behaviour I've expected implemented since CMake 3.1.0 (see CMP0054).
Output of the 3.0.2 test:
CMake version: 3.0.2
Quoted test
Surprise!
Unquoted test
Surprise!
Output of the 3.1.0 test:
CMake version: 3.1.0
Quoted test
OK
Unquoted test
Surprise!
You ran into a rather annoying "it's not a bug, it's a feature" behavior of CMake. As explained in the documentation of the if command:
The if command was written very early in CMake's history, predating the ${}
variable evaluation syntax, and for convenience evaluates variables named
by its arguments as shown in the above signatures.
Well, the convenience turned out to be an inconvenience. In your example the the string "d" is treated as a variable named d by the if command. If the variable d happens to be defined to the empty string, the message statement will print "oops...", e.g.:
set (d "")
if("d" STREQUAL "")
# this branch will be taken
message("oops...")
else()
message("fine")
endif()
This can give surprising results for statements like
if("${A}" STREQUAL "some string")
because there can be an unintended double expansion of the first argument if the variable A happens to be defined to a string which is also the name of a CMake variable, e.g.:
set (A "d")
set (d "some string")
if("${A}" STREQUAL "some string")
# this branch will be taken
message("oops...")
else()
message("fine")
endif()
Possible work-arounds:
You can add a suffix character to the string after the ${} expansion which prevents the if statement from doing the automatic evaluation:
set (A "d")
set (d "some string")
if("${A} " STREQUAL "some string ")
message("oops...")
else()
# this branch will be taken
message("fine")
endif()
Do not use ${} expansion:
set (A "d")
set (d "some string")
if(A STREQUAL "some string")
message("oops...")
else()
# this branch will be taken
message("fine")
endif()
To prevent unintended evaluation on the right side of STREQUAL use MATCHES with a CMake regular expression instead:
if(A MATCHES "^value$")
...
endif()
Addendum: CMake 3.1 no longer does double expansions for quoted arguments. See the new policy.
As of CMake 3.1, there are new rules variable expansions in if(). They are enabled if you either:
set cmake_minimum_required(3.1) (or higher) at the top of you project file, or
use a lower minimum version number but manually set policy CMP0054 to NEW.
Even in that case, it remains true is that the first argument to if is expanded with the value of a variable matching that name, if it exists:
set (d "")
if(d STREQUAL "")
# this branch will be taken
message("oops...")
else()
message("fine")
endif()
However, this is now disabled if the first argument is quoted:
set (d "")
if("d" STREQUAL "")
message("oops...")
else()
# due to quotes around "d" in if statement,
# this branch will be taken
message("fine")
endif()
If you do want to test a variable's contents against a value, you can either use the classic unquoted syntax, or use the "${d}" syntax you suggested. Thanks to the new rules, this will never suffer the double-expansion problem mentioned in sakra's answer:
set (A "d")
set (d "some string")
if("${A}" STREQUAL "d")
# this branch will be taken
message("fine")
elseif("${A}" STREQUAL "some string")
message("oops...")
else()
message("??")
endif()