Calling a CMake function: number of arguments - cmake

There is a function named myfunc defined as
function (myfunc var1 var2 var3)
...
endfunction()
Then, I see a function call
myfunc(custom hahaha platform ALL
COMMAND echo "hello world"
SOURCES ${some_var} )
My question is
The function myfunc takes 3 variables. In the above function call, which are the 3 variables? Also, how can there be additional commands COMMAND and SOURCES within the function call?

3 variables will be the first 3 arguments.
If your function was defined as follows:
function (myfunc var1 var2 var3)
message ("var1: ${var1}")
message ("var2: ${var2}")
message ("var3: ${var3}")
message ("number of arguments sent to function: ${ARGC}")
message ("all function arguments: ${ARGV}")
message ("all arguments beyond defined: ${ARGN}")
endfunction()
after calling it as you stated:
set (some_var "some var")
myfunc(custom hahaha platform ALL
COMMAND echo "hello world"
SOURCES ${some_var} )
the outuput will be:
var1: custom
var2: hahaha
var3: platform
number of arguments sent to function: 9
all function arguments: custom;hahaha;platform;ALL;COMMAND;echo;hello world;SOURCES;some var
all arguments beyond defined: ALL;COMMAND;echo;hello world;SOURCES;some var
so you have called your function with 9 arguments, that are referenced with ${ARGV}, all arguments that are not defined can also be referenced using variable ${ARGN}.
Note that when calling function, ALL, COMMAND, and SOURCES are just arguments to the function, nothing else.
In the end, here is the full documentation about cmake functions and arguments

To complement #gordan.sikic answer, you also might be interested in cmake_parse_arguments command.
It allows you to define named parameters to your function, like COMMAND ... and WORKING_DIRECTORY ... in add_custom_command. See example in the documentation page.

Related

How to print a message exactly once per cmake invocation?

Wanting to cause a package foobar to print where it was found, when using
find_package(foobar CONFIG)
I am using
find_package_message(foobar
"Found foobar: ${info} (version ${foobar_VERSION})"
"[${info}][${foobar_VERSION}]"
)
The idea of using find_package_message is to only
print this message once.
However, I want to print it every time cmake is run from the start.
I only want to avoid duplicates during the same run of cmake.
find_package_message stores a variable in the cache (FIND_PACKAGE_MESSAGE_DETAILS_foobar)
containing the value of the above third argument ("[${info}][${foobar_VERSION}]") and
prints the message again when that variable doesn't exist or changed.
So, the result of running cmake a second time is that nothing is printed: FIND_PACKAGE_MESSAGE_DETAILS_foobar already exists in the cache and didn't change.
How can I fix this to print a message once every new invocation of cmake?
Function find_package_message is intended for print the message once until "details" are changed. For achieve different semantic - print message once per cmake invocation - there are a little sense to use this function but implement your own one.
For differentiate the first function invocation from further ones one may check whether GLOBAL property is defined:
function(print_message_once name message)
# Name of the custom GLOBAL property to check
set(pname PRINT_MESSAGE_ONCE_DUMMY_${name})
get_property(prop_defined GLOBAL PROPERTY ${pname} DEFINED)
if (NOT prop_defined)
message(STATUS "${message}")
# Define a property so next time it will exist
define_property(GLOBAL PROPERTY ${pname} BRIEF_DOCS "${name}" FULL_DOCS "${name}")
endif()
endfunction()
Note, this function is no longer requires details argument. It is very unlikely that during a single cmake invocation one will clear the cache after the first finding the package and performs second search with different parameters.
Alternatively, instead of property check, one may check existence of the function:
function(print_message_once name message)
# Name of the custom function to check
set(fname _check_first_dummy_${name})
if (NOT COMMAND ${fname})
message(STATUS "${message}")
# Define a function so next time it will exist
function(${fname})
endfunction()
endif()
endfunction()
Usage of function print_message_once (defined above using any of 2 ways) "compatible" with find_package_message is
if(NOT foobar_FIND_QUIETLY)
print_message_once(foobar
"Found foobar: ${info} (version ${foobar_VERSION})"
)
endif()
If desired, checking for XXX_FIND_QUIETLY variable (which reflects QUIET option of find_package() call) could be incorporated into print_message_once function itself.

Why is my cmake function not assigning value to argument?

I write this function that takes a list and appends new values to it. When
I print it only prints dir.
function(test dst_list)
# do somethin
set(my_list "dir1" "dir2")
set(${dst_list} ${my_list})
# message(${dst_list})
endfunction()
set(my_list "dir")
test(my_list)
message("${my_list}")
Trick here if you set() some variable before function and then change it in function and returned so you can read new value, you must add to function bare variable name as in your set() and separately variable value. Other trick you must use is PARENT_SCOPE so set() variables in function can be returned (I think in this case they are rewritten by the same name).
function(test var_rtn var_val)
set(my_list "dir1" "dir2")
set(${var_rtn} ${var_val} ${my_list} PARENT_SCOPE)
endfunction()
set(my_list "dir")
test(my_list "${my_list}")
message("ANSWER: ${my_list}")
Your output will be now: ANSWER: dir;dir1;dir2

Options for user-defined CMake functions

function(print2Args arg1 arg2)
message(STATUS ${arg1} " " ${arg2})
endfunction(print2Args)
Is it possible to update user defined function print2Args s.t. it will accept options like the built-in CMake function execute_process?
CMake offers this via the CMakeParseArguments. You do not specify the arguments in the function signature as you did in your example.
CMake accepts more arguments then given in the function signature. You define options, one-valued arguments and pairs of arguments in variables and pass these to cmake_parse_arguments. This command sets several variables which you can use to check what arguments where set.
Documentation and an example:
https://cmake.org/cmake/help/latest/module/CMakeParseArguments.html

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

Optional Argument in cmake macro

I want to create a macro whose argument is optional. If not specified, the argument's value should be an empty string. How can I do this?
Any arguments named in your macro definition are required for callers. However, your macro can still accept optional arguments as long as they aren't explicitly named in the macro definition.
That is, callers are permitted to pass in extra arguments (which were not named in the macro's argument list). Your macro can check for the existence of such extra arguments by checking the ${ARGN} variable.
Beware: ARGN is not a "normal" cmake variable. To use it with the list() command, you'll need to copy it into a normal variable first:
macro (mymacro required_arg1 required_arg2)
# Cannot use ARGN directly with list() command,
# so copy it to a variable first.
set (extra_args ${ARGN})
# Did we get any optional args?
list(LENGTH extra_args extra_count)
if (${extra_count} GREATER 0)
list(GET extra_args 0 optional_arg)
message ("Got an optional arg: ${optional_arg}")
endif ()
endmacro (mymacro)
CMake does not (AFAIK) check the number of variables passed to a macro, so you can just go ahead and declare it as any other macro.
There is also a variable ${ARGN} which expands to a list of all the "remaining" variables passed to a macro, which may be useful.
Update
As stated in Sam's comment, CMake now fails unless all expected (named) arguments are given when calling a macro or function.
This happens by default if the argument isn't specified as CMake doesn't check the number of parameters required for a macro.