Fonction vs macro dans CMake

89

Le document officiel de CMake 2.8.12 parle demacro

Lorsqu'elle est invoquée, les commandes enregistrées dans la macro sont d'abord modifiées en remplaçant les paramètres formels ($ {arg1}) par les arguments passés, puis invoquées comme des commandes normales.

Et à propos function

Lorsqu'elle est invoquée, les commandes enregistrées dans la fonction sont d'abord modifiées en remplaçant les paramètres formels ($ {arg1}) par les arguments passés, puis invoquées comme des commandes normales.

Évidemment, deux citations sont presque identiques mais me déroutent. Remplace-t-il d'abord les paramètres lors de l'appel d'une fonction comme une macro?

Yantao Xie
la source
8
Il existe au moins une autre différence importante, quoique assez évidente entre functionet macro: la sémantique de return(): Lorsqu'il est utilisé dans a macro, vous ne reviendrez pas de la macro mais de la fonction appelante.
Joachim W le
1
Une autre note importante est qu'une macro a une étape d'expansion en deux passes sur les arguments lorsqu'une fonction n'en est qu'une. Essayez de créer ces macros et fonctions, et imprimez le ${ARGV}de l'intérieur: macro(my_macro), function(my_func). Et les utiliser: set(a 123), my_macro("\\\${a}\\\\;\\\;;"), my_func(\${a}\\;\;;). Vous constaterez que vous devez échapper à double tout $, \ , ;pour passer correctement chaîne entière sans changement aux commandes imbriquées. C'est réel dans le cmake 3.14+.
Andry

Réponses:

93

J'ai écrit un exemple de code ci-dessous:

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

et la sortie est:

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

Il semble argdonc que la valeur de l' varappel soit attribuée Fooet ${arg}que la chaîne soit simplement remplacée par une chaîne ${var}lors de l'appel Moo.

Je pense donc que les deux citations ci-dessus sont très faciles à confondre, bien que les documents officiels disaient également que :

Notez que les paramètres d'une macro et les valeurs telles que ARGN ne sont pas des variables au sens habituel de CMake. Ce sont des remplacements de chaînes comme le ferait le préprocesseur C avec une macro. Si vous voulez de vraies variables CMake et / ou un meilleur contrôle de la portée CMake, vous devriez regarder la commande de fonction.

Yantao Xie
la source
J'ai oublié cela, mais je pense que c'est peut-être le cas.
Yantao Xie
2
@robert Répondre immédiatement à votre propre question est autorisé selon le Centre d'aide (surtout s'il s'agit d'une bonne question non dupliquée d'intérêt général pour les autres). Il s'agit d'aider SO à devenir une meilleure base de connaissances. Avez-vous lu l'article de blog lié à cette rubrique du centre d'aide? stackoverflow.blog/2011/07/01/…
Emile Cormier
1
@robert Je ne fais que relayer ce que le fondateur de SO lui-même pense de cette pratique. Prenez-le avec lui. ;-)
Emile Cormier
2
Exécuter des exemples comme celui-ci avec cmake --trace-expandest éclairant
MarcH
1
Pensez à ajouter la commande suivante après chaque appel: message("# arg in main scope = '${arg}'")+ appeler la fonction avant la macro.
MarcH
34

En d'autres termes, la fonction pousse et affiche une nouvelle portée de variable (les variables créées et modifiées n'existent que dans la fonction), ce n'est pas le cas de la macro. Cependant, vous pouvez remplacer le comportement par défaut de la fonction avec le PARENT_SCOPEparamètre de la setcommande.

Robert
la source
8

La documentation cmake que vous avez citée est tellement trompeuse qu'elle est fondamentalement erronée. Il devrait être clarifié / corrigé comme ceci:

  • macro: lorsqu'elle est invoquée, les commandes enregistrées dans la macro sont d' abord toutes modifiées avant toute exécution en remplaçant les paramètres formels ($ {arg1}) par les arguments passés.

cmake --trace-expand montre exactement ce qui se passe.

Le document cmake 3.13.3 n'a pas changé par rapport à 2.8.12 à ce sujet.

Mars
la source
3

Une autre différence notable entre function()et macro()est le comportement de return().

À partir de la documentation cmake de return () :

Notez qu'une macro, contrairement à une fonction, est développée sur place et ne peut donc pas gérer return ().

Donc, parce qu'il est étendu sur place, dans un macro()il revient de l'appelant. Dans une fonction, il quitte simplement lefunction()

Exemple:

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
la source
2

L' expansion macro, répondue par Yantao Xie, m'ouvre vraiment les yeux!

J'ai également trouvé que le didacticiel ci-dessous contient des exemples concrets, ce qui est utile pour comprendre le concept de portée variable.

Cité de Learn cmake in 15 minutes :

Dans CMake, vous pouvez utiliser une paire de commandes function/ endfunctionpour définir une fonction. En voici un qui double la valeur numérique de son argument, puis imprime le résultat:

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

doubleIt("4")                           # Prints: 8

Les fonctions s'exécutent dans leur propre étendue. Aucune des variables définies dans une fonction ne pollue la portée de l'appelant. Si vous souhaitez renvoyer une valeur, vous pouvez passer le nom d'une variable à votre fonction, puis appeler la setcommande avec l'argument spécial 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

De même, une paire de commandes macro/ endmacrodéfinit une macro. Contrairement aux fonctions, les macros s'exécutent dans la même portée que leur appelant. Par conséquent, toutes les variables définies dans une macro sont définies dans la portée de l'appelant. Nous pouvons remplacer la fonction précédente par la suivante:

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

Les fonctions et les macros acceptent un nombre arbitraire d'arguments. Les arguments sans nom sont exposés à la fonction sous forme de liste, via une variable spéciale nommée ARGN.

Voici une fonction qui double chaque argument qu'elle reçoit, en imprimant chacun sur une ligne distincte:

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
la source