Função vs. Macro no CMake

90

O documento oficial do CMake 2.8.12 diz sobremacro

Quando é invocado, os comandos gravados na macro são primeiro modificados pela substituição dos parâmetros formais ($ {arg1}) pelos argumentos passados ​​e, em seguida, invocados como comandos normais.

e sobre function

Quando é invocado, os comandos registrados na função são primeiro modificados pela substituição dos parâmetros formais ($ {arg1}) pelos argumentos passados ​​e, em seguida, invocados como comandos normais.

Obviamente, duas citações são quase iguais, mas me confundem. Substitui os parâmetros primeiro ao chamar uma função como a macro?

Yantao Xie
fonte
8
Há pelo menos uma outra diferença importante, embora bastante óbvia entre functione macro: a semântica de return(): Quando usado em a macro, você não retornará da macro, mas da função de chamada.
Joachim W
1
Outra observação importante é que uma macro tem um estágio de expansão de duas passagens nos argumentos quando uma função é apenas uma. Tente criar essas macros e funções, e imprima ${ARGV}de dentro: macro(my_macro), function(my_func). E usá-los: set(a 123), my_macro("\\\${a}\\\\;\\\;;"), my_func(\${a}\\;\;;). Você vai descobrir que você tem que dupla de escape todo o $, \ , ;para passar corretamente toda corda inalterada aos comandos aninhados. Isso é real no cmake 3.14+.
Andry

Respostas:

95

Escrevi um código de amostra abaixo:

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})

e a saída é:

=== Call macro ===
arg = ABC
# After change the value of arg.
arg = ABC
=== Call function ===
arg = ABC
# After change the value of arg.
arg = abc

Portanto, parece que argé atribuído o valor de varao chamar Fooe ${arg}é apenas substituído por string ${var}ao chamar Moo.

Portanto, acho que as duas citações acima são muito fáceis de confundir, embora os documentos oficiais também dissessem que :

Observe que os parâmetros de uma macro e valores como ARGN não são variáveis ​​no sentido usual do CMake. Eles são substituições de strings, de modo muito parecido com o que o pré-processador C faria com uma macro. Se você deseja variáveis ​​CMake verdadeiras e / ou melhor controle de escopo CMake, você deve olhar o comando de função.

Yantao Xie
fonte
Esqueci disso, mas acho que pode ser.
Yantao Xie
2
@robert Responder sua própria pergunta imediatamente é permitido de acordo com a Central de Ajuda (especialmente se for uma pergunta válida e não duplicada de interesse geral para terceiros). Isso ajuda o SO a se tornar uma melhor base de conhecimento. Você leu a postagem do blog com link nesse tópico da Central de Ajuda? stackoverflow.blog/2011/07/01/…
Emile Cormier
1
@robert, estou apenas relatando o que o próprio fundador do SO pensa sobre essa prática. Fale com ele. ;-)
Emile Cormier
2
Executar exemplos como este com cmake --trace-expandé esclarecedor
MarcH
1
Considere adicionar o seguinte comando após cada chamada: message("# arg in main scope = '${arg}'")+ chamar a função antes da macro.
MarcH
34

Em outras palavras, a função empurra e exibe um novo escopo de variável (as variáveis ​​criadas e alteradas existem apenas na função), a macro não. No entanto, você pode substituir o comportamento padrão da função com o PARENT_SCOPEparâmetro do setcomando.

robert
fonte
8

A documentação do cmake que você citou é tão enganosa que está basicamente errada. Deve ser esclarecido / corrigido assim:

  • macro: quando é invocado, os comandos gravados na macro são primeiro todos modificados antes de qualquer um ser executado , substituindo os parâmetros formais ($ {arg1}) pelos argumentos passados.

cmake --trace-expand mostra exatamente o que acontece.

O doc cmake 3.13.3 não mudou em comparação com 2.8.12 com relação a isso.

Marcha
fonte
3

A expansão macro, respondida por Yantao Xie realmente abre meus olhos!

Também descobri que o tutorial abaixo vem com alguns exemplos concretos, o que é útil para entender o conceito de escopo de variável.

Citado em Learn cmake in 15 mins :

No CMake, você pode usar um par de function/ endfunctioncomandos para definir uma função. Aqui está um que duplica o valor numérico de seu argumento e, em seguida, imprime o resultado:

function(doubleIt VALUE)
    math(EXPR RESULT "${VALUE} * 2")
    message("${RESULT}")
endfunction()

doubleIt("4")                           # Prints: 8

As funções são executadas em seu próprio escopo. Nenhuma das variáveis ​​definidas em uma função poluem o escopo do chamador. Se quiser retornar um valor, você pode passar o nome de uma variável para sua função e, em seguida, chamar o setcomando com o argumento especial 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

Da mesma forma, um par de comandos macro/ endmacrodefine uma macro. Ao contrário das funções, as macros são executadas no mesmo escopo de seu chamador. Portanto, todas as variáveis ​​definidas dentro de uma macro são definidas no escopo do chamador. Podemos substituir a função anterior pela seguinte:

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

Ambas as funções e macros aceitam um número arbitrário de argumentos. Argumentos sem nome são expostos à função como uma lista, por meio de uma variável especial chamada ARGN.

Aqui está uma função que dobra todos os argumentos que recebe, imprimindo cada um em uma linha separada:

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
fonte
3

Outra diferença notável entre function()e macro()é o comportamento de return().

Da documentação cmake de return () :

Observe que uma macro, ao contrário de uma função, é expandida no local e, portanto, não pode manipular return ().

Portanto, como ele é expandido no local, macro()ele retorna do chamador. Enquanto em uma função, ele apenas sai dofunction()

Exemplo:

macro(my_macro)
    return()
endmacro()

function(my_function)
    return()
endfunction()

my_function()
message(hello) # is printed
my_macro()
message(hi) # is not printed
Adam Zahran
fonte