Est-il préférable de spécifier les fichiers source avec GLOB ou chaque fichier individuellement dans CMake?


157

CMake propose plusieurs façons de spécifier les fichiers source d'une cible. La première consiste à utiliser le globbing ( documentation ), par exemple:

FILE(GLOB MY_SRCS dir/*)

Une autre méthode consiste à spécifier chaque fichier individuellement.

Quelle voie est préférée? Globbing semble facile, mais j'ai entendu dire que cela avait des inconvénients.

Réponses:


185

Divulgation complète: à l'origine, j'avais préféré l'approche globbing pour sa simplicité, mais au fil des années, j'ai fini par reconnaître que la liste explicite des fichiers est moins sujette aux erreurs pour les grands projets multi-développeurs.

Réponse originale:


Les avantages du globbing sont:

  • Il est facile d'ajouter de nouveaux fichiers car ils ne sont répertoriés qu'à un seul endroit: sur le disque. L'absence de globalisation crée une duplication.

  • Votre fichier CMakeLists.txt sera plus court. C'est un gros plus si vous avez beaucoup de fichiers. L'absence de globalisation vous fait perdre la logique CMake parmi d'énormes listes de fichiers.

Les avantages de l'utilisation de listes de fichiers codées en dur sont:

  • CMake suivra correctement les dépendances d'un nouveau fichier sur le disque - si nous utilisons glob, les fichiers non globalisés la première fois lorsque vous avez exécuté CMake ne seront pas récupérés

  • Vous vous assurez que seuls les fichiers souhaités sont ajoutés. Globbing peut ramasser des fichiers parasites dont vous ne voulez pas.

Afin de contourner le premier problème, vous pouvez simplement «toucher» le CMakeLists.txt qui fait le glob, soit en utilisant la commande tactile ou en écrivant le fichier sans modifications. Cela forcera CMake à réexécuter et à récupérer le nouveau fichier.

Pour résoudre le deuxième problème, vous pouvez organiser soigneusement votre code dans des répertoires, ce que vous faites probablement de toute façon. Dans le pire des cas, vous pouvez utiliser la list(REMOVE_ITEM)commande pour nettoyer la liste globale des fichiers:

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})

La seule situation réelle où cela peut vous mordre est si vous utilisez quelque chose comme git-bisect pour essayer d'anciennes versions de votre code dans le même répertoire de construction. Dans ce cas, vous devrez peut-être nettoyer et compiler plus que nécessaire pour vous assurer d'obtenir les bons fichiers dans la liste. C'est un cas tellement compliqué, et où vous êtes déjà sur vos gardes, que ce n'est pas vraiment un problème.


1
Aussi mauvais avec le globbing: les fichiers difftool de git sont stockés comme $ basename. $ Ext. $ Type. $ Pid. $ Ext, ce qui peut provoquer des erreurs amusantes lors de la tentative de compilation après une seule résolution de fusion.
mathstuf

9
Je pense que cette réponse passe sous silence les inconvénients de cmake manquant de nouveaux fichiers, Simply "touch" the CMakeLists.txtc'est OK si vous êtes le développeur, mais pour d'autres qui construisent votre logiciel, cela peut vraiment être un problème que votre construction échoue après la mise à jour et qu'il incombe à eux d'enquêter. Pourquoi.
ideasman42

36
Vous savez quoi? Depuis que j'ai écrit cette réponse il y a 6 ans , j'ai un peu changé d'avis et je préfère maintenant lister explicitement les fichiers. Son seul inconvénient est que "c'est un peu plus de travail pour ajouter un fichier", mais cela vous évite toutes sortes de maux de tête. Et à bien des égards, il vaut mieux explicite qu'implicite.
richq

1
@richq Ce hook git vous ferait-il reconsidérer votre position actuelle? :)
Antonio

8
Eh bien, comme le dit Antonio, les votes ont été donnés pour préconiser l'approche «globbing». Changer la nature de la réponse est une chose à faire pour ces électeurs. En guise de compromis, j'ai ajouté une modification pour refléter mon opinion modifiée. Je m'excuse auprès d'Internet d'avoir provoqué une telle tempête dans une tasse de thé :-P
richq

113

La meilleure façon de spécifier les fichiers source dans CMake est de les lister explicitement .

Les créateurs de CMake eux-mêmes conseillent de ne pas utiliser de globbing.

Voir: https://cmake.org/cmake/help/v3.15/command/file.html?highlight=glob#file

(Nous ne recommandons pas d'utiliser GLOB pour collecter une liste de fichiers source à partir de votre arborescence source. Si aucun fichier CMakeLists.txt ne change lorsqu'une source est ajoutée ou supprimée, le système de construction généré ne peut pas savoir quand demander à CMake de se régénérer.)

Bien sûr, vous voudrez peut-être savoir quels sont les inconvénients - continuez à lire!


Quand Globbing échoue:

Le gros inconvénient du globbing est que la création / suppression de fichiers ne met pas automatiquement à jour le système de construction.

Si vous êtes la personne qui ajoute les fichiers, cela peut sembler un compromis acceptable, mais cela pose des problèmes pour les autres personnes qui construisent votre code, ils mettent à jour le projet à partir du contrôle de version, exécutent la version, puis vous contactent, se plaignant que
"la version cassé".

Pour aggraver les choses, l'échec donne généralement une erreur de liaison qui ne donne aucune indication sur la cause du problème et du temps est perdu pour le dépanner.

Dans un projet sur lequel j'ai travaillé, nous avons commencé le globbing, mais nous avons reçu tellement de plaintes lorsque de nouveaux fichiers ont été ajoutés, que c'était une raison suffisante pour lister explicitement les fichiers au lieu de globbing.

Cela rompt également les flux de travail git courants
( git bisectet le basculement entre les branches de fonctionnalités).

Je ne peux donc pas recommander cela, les problèmes que cela pose l'emportent de loin sur la commodité, lorsque quelqu'un ne peut pas créer votre logiciel à cause de cela, il peut perdre beaucoup de temps pour trouver le problème ou simplement abandonner.

Et une autre note, se souvenir de toucher CMakeLists.txtn'est pas toujours suffisant, avec des builds automatisés qui utilisent le globbing, je devais exécuter cmakeavant chaque build car des fichiers pouvaient avoir été ajoutés / supprimés depuis le dernier bâtiment *.

Exceptions à la règle:

Il y a des moments où le globbing est préférable:

  • Pour configurer des CMakeLists.txtfichiers pour des projets existants qui n'utilisent pas CMake.
    C'est un moyen rapide d'obtenir toutes les sources référencées (une fois que le système de construction est en cours d'exécution - remplacez le globbing par des listes de fichiers explicites).
  • Lorsque CMake n'est pas utilisé comme système de construction principal , si par exemple vous utilisez un projet qui n'utilise pas CMake et que vous souhaitez maintenir votre propre système de construction pour cela.
  • Pour toute situation où la liste de fichiers change si souvent qu'il devient impossible de la maintenir. Dans ce cas, cela pourrait être utile, mais vous devez accepter de courir cmakepour générer des fichiers de construction à chaque fois pour obtenir une construction fiable / correcte (ce qui va à l'encontre de l'intention de CMake - la possibilité de séparer la configuration de la construction) .

* Oui, j'aurais pu écrire un code pour comparer l'arborescence des fichiers sur le disque avant et après une mise à jour, mais ce n'est pas une solution de contournement si agréable et quelque chose de mieux laissé au système de construction.


9
"Le gros inconvénient du globbing est que la création de nouveaux fichiers ne met pas automatiquement à jour le système de construction." Mais n'est-il pas vrai que si vous ne glissez pas, vous devez toujours mettre à jour manuellement CMakeLists.txt, ce qui signifie que cmake ne met toujours pas à jour automatiquement le système de construction? Il semble que dans les deux cas, vous devez vous rappeler de faire quelque chose manuellement pour que les nouveaux fichiers soient créés. Toucher CMakeLists.txt semble plus facile que de l'ouvrir et de le modifier pour ajouter le nouveau fichier.
Dan

17
@Dan, pour votre système - bien sûr, si vous ne développez que seul, c'est bien, mais qu'en est-il de tous les autres qui construisent votre projet? allez-vous leur envoyer un e-mail pour qu'ils touchent manuellement le fichier CMake? chaque fois qu'un fichier est ajouté ou supprimé? - Le stockage de la liste de fichiers dans CMake garantit que la construction utilise toujours les mêmes fichiers que vcs connaît. Croyez-moi - ce n'est pas juste un détail subtil - Lorsque votre build échoue pour de nombreux développeurs - ils publient des listes de diffusion et demandent sur IRC que le code est cassé. Remarque: (Même sur votre propre système, vous pouvez revenir dans l'historique de git par exemple, et ne pas penser à entrer et à toucher les fichiers CMake)
ideasman42

2
Ah je n'avais pas pensé à cette affaire. C'est la meilleure raison que j'ai entendue contre le globbing. Je souhaite que les documents cmake expliquent pourquoi ils recommandent aux gens d'éviter le globbing.
Dan

1
J'ai réfléchi à la solution d'écrire l'horodatage de la dernière exécution de cmake dans un fichier. Les seuls problèmes sont: 1) cela doit probablement être fait par cmake pour être crossplatform et nous devons donc éviter que cmake ne s'exécute une deuxième fois. 2) Peut-être plus de conflits de fusion (qui se produisent encore avec la liste de fichiers btw) Ils pourraient en fait être résolus de manière triviale dans ce cas en prenant un horodatage ultérieur.
Predelnik

2
@ tim-mb, "Mais ce serait bien si CMake créait un fichier filetree_updated que vous pourriez archiver, qu'il changerait automatiquement chaque fois que le glob de fichiers mis à jour." - vous venez de décrire exactement ce que fait ma réponse.
Glen Knowles

22

Dans CMake 3.12, les commandes file(GLOB ...)etfile(GLOB_RECURSE ...) ont obtenu une CONFIGURE_DEPENDSoption qui réexécute cmake si la valeur du glob change. Comme c'était le principal inconvénient du globbing pour les fichiers source, il est maintenant possible de le faire:

# Whenever this glob's value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")

add_executable(my_target ${sources})

Cependant, certaines personnes recommandent toujours d'éviter le globbing pour les sources. En effet, la documentation déclare:

Nous vous déconseillons d'utiliser GLOB pour collecter une liste de fichiers source à partir de votre arborescence source. ... Le CONFIGURE_DEPENDSdrapeau peut ne pas fonctionner de manière fiable sur tous les générateurs, ou si un nouveau générateur est ajouté à l'avenir qui ne peut pas le prendre en charge, les projets qui l'utilisent seront bloqués. Même si cela CONFIGURE_DEPENDSfonctionne de manière fiable, il y a toujours un coût pour effectuer la vérification à chaque reconstruction.

Personnellement, je considère que les avantages de ne pas avoir à gérer manuellement la liste des fichiers source l'emportent sur les inconvénients possibles. Si vous devez revenir aux fichiers répertoriés manuellement, cela peut être facilement réalisé en imprimant simplement la liste des sources globales et en la recollant.


Si votre système de construction effectue un cycle complet de cmake et de construction (supprimez le répertoire de construction, exécutez cmake à partir de là, puis appelez le makefile), à ​​condition qu'ils ne récupèrent pas de fichiers indésirables, il n'y a sûrement aucun inconvénient à utiliser les sources GLOBbed? D'après mon expérience, la partie cmake s'exécute beaucoup plus rapidement que la construction, donc ce n'est pas vraiment une surcharge de toute façon
Den-Jason

9

Vous pouvez glober en toute sécurité (et devrait probablement) au prix d'un fichier supplémentaire pour contenir les dépendances.

Ajoutez des fonctions comme celles-ci quelque part:

# Compare the new contents with the existing file, if it exists and is the 
# same we don't want to trigger a make by changing its timestamp.
function(update_file path content)
    set(old_content "")
    if(EXISTS "${path}")
        file(READ "${path}" old_content)
    endif()
    if(NOT old_content STREQUAL content)
        file(WRITE "${path}" "${content}")
    endif()
endfunction(update_file)

# Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with
# the list of dependencies in it - this file should be treated as part of 
# CMakeLists.txt (source controlled, etc.).
function(update_deps_file deps)
    set(deps_file "CMakeDeps.cmake")
    # Normalize the list so it's the same on every machine
    list(REMOVE_DUPLICATES deps)
    foreach(dep IN LISTS deps)
        file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep})
        list(APPEND rel_deps ${rel_dep})
    endforeach(dep)
    list(SORT rel_deps)
    # Update the deps file
    set(content "# generated by make process\nset(sources ${rel_deps})\n")
    update_file(${deps_file} "${content}")
    # Include the file so it's tracked as a generation dependency we don't
    # need the content.
    include(${deps_file})
endfunction(update_deps_file)

Et puis allez globbing:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})

Vous êtes toujours en train de faire le tour des dépendances explicites (et de déclencher toutes les constructions automatisées!) Comme avant, seulement c'est dans deux fichiers au lieu d'un.

Le seul changement de procédure intervient après la création d'un nouveau fichier. Si vous ne globez pas le flux de travail consiste à modifier CMakeLists.txt à partir de Visual Studio et à reconstruire, si vous faites glob, vous exécutez cmake explicitement - ou touchez simplement CMakeLists.txt.


Au début, je pensais que c'était un outil qui mettrait automatiquement à jour les Makefiles lorsqu'un fichier source est ajouté, mais je vois maintenant quelle est sa valeur. Agréable! Cela résout le problème de quelqu'un mettant à jour à partir du référentiel et ayant makedonné d'étranges erreurs de l'éditeur de liens.
Cris Luengo le

1
Je pense que cela pourrait être une bonne méthode. Bien sûr, il ne faut pas oublier de déclencher cmake après l'ajout ou la suppression d'un fichier, et il est également nécessaire de valider ce fichier de dépendance, donc une formation du côté de l'utilisateur est nécessaire. L'inconvénient majeur pourrait être que ce fichier de dépendances pourrait être à l'origine de conflits de fusion désagréables qui pourraient être difficiles à résoudre sans exiger à nouveau que le développeur ait une certaine compréhension du mécanisme.
Antonio

1
Cela ne fonctionnera pas si votre projet a inclus des fichiers conditionnellement (par exemple, certains fichiers qui ne sont utilisés que lorsqu'une fonctionnalité est activée, ou uniquement utilisés pour un système d'exploitation particulier). Il est assez courant pour les logiciels portables que certains fichiers ne sont utilisés que pour des plates-formes spécifiques.
ideasman42

0

Spécifiez chaque fichier individuellement!

J'utilise un CMakeLists.txt conventionnel et un script python pour le mettre à jour. J'exécute le script python manuellement après avoir ajouté des fichiers.

Voir ma réponse ici: https://stackoverflow.com/a/48318388/3929196

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.