web-dev-qa-db-fra.com

Pourquoi CMake fait-il une distinction entre une "cible" et une "commande"?

Dans la sémantique de CMake, il existe une sorte de distinction entre "cibles" et commandes "qui me déconcerte. Dans Makefiles, cette distinction n'existe pas:

targetname:dependency
    command

c'est-à-dire que les cibles correspondent à un fichier généré du même nom.

Dans CMake, vous avez des commandes telles que "add_custom_command" et "add_custom_target" qui ont des fonctionnalités qui se chevauchent, et même dans la documentation officielle, la sémantique est confuse, c'est-à-dire dans "Mastering CMake, 5ème édition", page 110 sous "Ajout d'une cible personnalisée":

L'argument DEPENDS établit une dépendance entre la cible personnalisée et les commandes personnalisées.

D'après ce que j'ai compris, les cibles (fichiers générés) ont des dépendances (autres fichiers, générés ou non) et une commande permettant d'effectuer la génération. Il est absurde de dire qu'une cible dépend d'une commande. Pour aggraver les choses, il existe deux types de "add_custom_command" qui associent une commande supplémentaire à une cible existante ou la crachent dans l’éther.

Quelqu'un peut-il s'il vous plaît expliquer pourquoi cette distinction existe même? 

45
Andrew Wagner

Les cibles

En général, les cibles comprennent des exécutables ou des bibliothèques définis en appelant add_executable ou add_library et pouvant comporter plusieurs propriétés set.

Ils peuvent avoir des dépendances les unes sur les autres, ce qui signifie que des cibles dépendantes seront construites après leurs dépendances.

Cependant, vous pouvez également définir des "cibles personnalisées" via add_custom_target . De la docs:

Ajoute une cible avec le nom donné qui exécute les commandes données. La cible n'a pas de fichier de sortie et est toujours considérée comme obsolète même si les commandes tentent de créer un fichier portant le nom de la cible. Utilisez ADD_CUSTOM_COMMAND pour générer un fichier avec des dépendances. Par défaut, rien ne dépend de la cible personnalisée. Utilisez ADD_DEPENDENCIES pour ajouter des dépendances vers ou à partir d'autres cibles.

Donc, elles sont différentes des cibles "normales" en ce qu'elles ne représentent pas des choses qui produiront un exe ou une lib, mais elles bénéficient toujours de toutes les propriétés que les cibles peuvent avoir, y compris avoir ou être des dépendances. Ils apparaissent comme une cible pouvant être construite (par exemple, make MyCustomTarget ou msbuild MyCustomTarget.vcxproj). Lorsque vous les construisez, vous appelez simplement les commandes qui leur ont été définies. S'ils ont des dépendances sur d'autres cibles (normales ou personnalisées), celles-ci seront construites en premier.


Commandes personnalisées

Une commande personnalisée définie via add_custom_command est assez différente en ce sens que ce n'est pas un objet "constructible" et n'a pas de propriétés réglables comme le fait une cible - ce n'est pas un objet nommé auquel on peut explicitement faire référence après l'ajout dans le CMakeLists.txt.

Il s’agit d’une commande (ou d’un ensemble de commandes) qui sera appelée avant la construction d’une cible dépendante. C'est tout ce que "dépend" signifie vraiment ici (du moins c'est ce que je vois) - cela signifie simplement que si A dépend de B, alors B sera construit/exécuté avant que A ne soit construit.

Les dépendants d'une commande personnalisée peuvent être définis explicitement à l'aide du formulaire add_custom_command(TARGET target ... ou implicitement en créant des cibles qui incluent les fichiers générés via le formulaire add_custom_command(OUTPUT output1 ....

Dans le premier cas, chaque fois que target est construit, la commande personnalisée est exécutée en premier.

Dans le second cas, c'est un peu plus complexe. Si la commande personnalisée a des cibles qui dépendent de son fichier de sortie (et que le fichier de sortie n'existe pas déjà), elle est appelée avant la construction de ces objets dépendants. Les dépendances sont créées implicitement lorsque vous exécutez, par exemple, add_library(MyLib output1.h ... )output1.h est un fichier généré via add_custom_command(OUTPUT output1.h ... ).

38
Fraser

add_custom_command ajoute une fonction appelable pouvant avoir des sorties définies (en utilisant les arguments OUTPUT et BYPRODUCTS). Il peut également avoir des dépendances qui seront exécutées avant l'appel de la fonction.

Notez qu'il ne fait PAS des choses que vous pouvez penser que cela est dû à une documentation étrange (les exemples de makefile sont très trompeurs). En particulier, il n'a aucune garantie quant au nombre de fois qu'il s'exécute. Par exemple, imaginez ceci:

add_custom_command(OUTPUT /tmp/touched COMMAND echo touch COMMAND touch /tmp/touched)
add_custom_target(touched-one ALL DEPENDS /tmp/touched)
add_custom_target(touched-two ALL DEPENDS /tmp/touched)

Combien de fois "touch" sera-t-il imprimé? Vous ne savez pas, puisque ce n'est spécifié nulle part; make -j2 l’imprimera probablement deux fois, mais cela dépend du temps:

Scanning dependencies of target touched-two
Scanning dependencies of target touched-one
[ 50%] Generating touched
touch
[100%] Generating touched
touch
[100%] Built target touched-two
[100%] Built target touched-one

Mais Ninja ne l’imprimera qu’une fois, probablement:

# rm -rf * && cmake -GNinja ../c ; cmake --build . -- -j 5
[1/1] Generating touched
touch

Habituellement, vous exécutez une commande add_custom_command afin de définir un travail et définissant une sortie, puis vous aurez un add_custom_target qui dépend du résultat de la commande personnalisée. Quiconque souhaite obtenir une sortie dépend de la cible et cela vous donne les garanties que vous souhaitez.

Mise en garde: voir ce bogue pour un bon exemple de la raison pour laquelle la construction d'outils métabuild multiplates-formes est REALLY HARD.

0
James Moore