106

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?

Shlomo Gottlieb
  • 519
  • 5
  • 11
Yantao Xie
  • 12,300
  • 15
  • 49
  • 79
  • 9
    There is at least one other important, albeit fairly obvious difference between `function` and `macro`: the semantics of `return()`: When used in a `macro`, you won't return from the macro but from the calling function. – Joachim W Sep 17 '15 at 16:06
  • 2
    One another important note, is that a macro have two-pass expansion stage on arguments when a function is only one. Try to create these macro and function, and print the `${ARGV}` from inside: `macro(my_macro)`, `function(my_func)`. And use them: `set(a 123)`, `my_macro("\\\${a}\\\\;\\\;;")` , `my_func(\${a}\\;\;;)`. You will find that you have to double escape all the `$`, `\ `, `;` to properly pass entire string unchanged to the nested commands. This is actual in the `cmake 3.14+`. – Andry May 29 '19 at 14:57

6 Answers6

117

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.

starball
  • 20,030
  • 7
  • 43
  • 238
Yantao Xie
  • 12,300
  • 15
  • 49
  • 79
  • I've forgotten that, but I think it may be. – Yantao Xie Jun 11 '16 at 03:05
  • @EmileCormier, my question was about posting a question just to answer it immediately, not about asking, doing research and finally answering it. – robert Jun 08 '17 at 15:53
  • 2
    @robert Answering your own question immediately is permitted as per the Help Center (especially if it's a good, non-duplicate question of general interest to others). This is to help SO become a better knowledge base. Did you read the blog post linked in that Help Center topic? https://stackoverflow.blog/2011/07/01/its-ok-to-ask-and-answer-your-own-questions/ – Emile Cormier Jun 08 '17 at 17:47
  • 1
    @robert I'm just relaying what the founder of SO himself thinks about this practice. Take it up with him. ;-) – Emile Cormier Jun 09 '17 at 22:19
  • I think it's important to note what happens with arg out of scope. Because for the function `arg=" "` and for the macro `arg=abc` holds true. – Chartas Jul 05 '18 at 19:08
  • 3
    Running examples like this with `cmake --trace-expand` is enlightening – MarcH Jan 24 '19 at 18:38
  • 1
    Please consider adding the following command after each call: `message("# arg in main scope = '${arg}'")` + calling the function before the macro. – MarcH Jan 24 '19 at 18:54
40

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.

bergercookie
  • 2,542
  • 1
  • 30
  • 38
robert
  • 3,539
  • 3
  • 35
  • 56
11

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.

MarcH
  • 18,738
  • 1
  • 30
  • 25
4

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
Izana
  • 2,537
  • 27
  • 33
3

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
Adham Zahran
  • 1,973
  • 2
  • 18
  • 35
0

Adding another difference, that I just stumbled upon.

macro (my_macro my_list)
  message ("[macro] my_list:'${my_list}'")
  list (LENGTH my_list len)
  message ("[macro] len: ${len}")
endmacro()

function (my_function my_list)
  message ("[function] my_list:'${my_list}'")
  list (LENGTH my_list len)
  message ("[function] len: ${len}")
endfunction()

set (A a b c)
my_macro("${A}")
my_function("${A}")

prints

[macro] my_list:'a;b;c'
[macro] len: 0
[function] my_list:'a;b;c'
[function] len: 3

With macros, it's as if the my_list var never existed and CMake performed a string replacement. The correct way with a macro would be

macro (my_macro my_list)
  message ("[macro] my_list:'${${my_list}}'")
  list (LENGTH "${my_list} len)
  message ("[macro] len: ${len}")
endmacro()

set (A a b c)
my_macro(A)
bartgol
  • 1,703
  • 2
  • 20
  • 30