Comment CMake est-il utilisé? [fermé]

95

Il est notoirement difficile d'obtenir des informations utiles sur CMake en tant que débutant. Jusqu'à présent, j'ai vu quelques tutoriels sur la façon de mettre en place un projet très basique ou un autre. Cependant, aucun de ceux-ci n'explique le raisonnement derrière tout ce qui y est montré, laissant toujours de nombreux trous à combler.

Qu'est-ce que l' appel CMake sur un CMakeLists signifie ? Est-il censé être appelé une fois par arbre de construction ou quoi? Comment utiliser des paramètres différents pour chaque build s'ils utilisent tous le même fichier CMakeLists.txt de la même source? Pourquoi chaque sous-répertoire a-t-il besoin de son propre fichier CMakeLists? Serait-il judicieux d'utiliser CMake sur un CMakeLists.txt autre que celui à la racine du projet? Si oui, dans quels cas? Quelle est la différence entre spécifier comment créer un exécutable ou une bibliothèque à partir du fichier CMakeLists dans son propre sous-répertoire et le faire dans le fichier CMakeLists à la racine de toutes les sources? Puis-je créer un projet pour Eclipse et un autre pour Visual Studio, en modifiant simplement l' -Goption lors de l'appel de CMake? Est-ce même ainsi qu'il est utilisé?

Aucun des didacticiels, pages de documentation ou questions / réponses que j'ai vus jusqu'à présent ne donne un aperçu utile pour comprendre comment utiliser CMake. Les exemples ne sont tout simplement pas exhaustifs. Quels que soient les tutoriels que je lis, j'ai l'impression de manquer quelque chose d'important.

Il y a beaucoup de questions posées par les débutants de CMake comme moi qui ne le demandent pas explicitement, mais qui rendent évident le fait qu'en tant que newbs, nous n'avons aucune idée de comment gérer CMake ou de quoi en faire.

leinaD_natipaC
la source
8
Vous devriez peut-être écrire un article de blog à la place.
steveire
2
Je suis tenté de voter de près "trop ​​large" ... Cela ressemble plus à un essai personnel sur CMake qu'à un bon ajustement pour stackoverflow. Pourquoi n'avez-vous pas posé toutes ces questions dans des questions distinctes? Et en quoi votre question + réponse est-elle différente de beaucoup d'autres, par exemple stackoverflow.com/q/20884380/417197 , stackoverflow.com/q/4745996/417197 , stackoverflow.com/q/4506193/417197 ?
André
13
@Andre Pourquoi cette question: J'ai passé une semaine à essayer de comprendre CMake et aucun tutoriel que j'ai trouvé n'était complet. Les liens que vous avez indiqués sont bons, mais pour quelqu'un qui a trouvé CMake parce qu'il a reçu un projet avec un tas de CMakeLists à l'intérieur, ils n'éclairent pas beaucoup sur le fonctionnement de CMake. J'ai en toute honnêteté essayé d'écrire une réponse que j'aimerais pouvoir me renvoyer à temps lorsque j'ai commencé à essayer d'apprendre CMake. Et les sous-questions visent à montrer que j'essaie en fait de demander ce que je devrais savoir pour ne pas avoir ces doutes. Poser la bonne question est DIFFICILE.
leinaD_natipaC
2
Pas de réponse, mais je vous invite à jeter un œil à jaws.rootdirectory.de . Alors que la documentation CMake (et la réponse acceptée ...) vous montre de nombreux extraits de ce que vous pourriez faire, j'ai rédigé une configuration de base (en aucun cas "complète" ou "parfaite"), y compris des commentaires tout au long, qui IMHO présente ce que CMake (et CTest et CPack et CDash ...) peut faire pour vous. Il est prêt à l'emploi et vous pouvez facilement l'adapter / l'étendre aux besoins de votre projet.
DevSolar
9
C'est dommage que de telles questions soient fermées de nos jours. Je pense que les questions générales qui nécessitent des tutoriels comme des réponses mais qui concernent des sujets mal / obscurément documentés (un autre exemple serait l'api Torch7 C) et les questions de niveau recherche devraient rester ouvertes. Ceux-ci sont bien plus intéressants que le typique "hé, j'ai un segfault lors de la réalisation d'un projet de jouet" qui afflige le site.
Ash

Réponses:

137

À quoi sert CMake?

Selon Wikipedia:

CMake est un logiciel [...] permettant de gérer le processus de construction d'un logiciel en utilisant une méthode indépendante du compilateur. Il est conçu pour prendre en charge les hiérarchies de répertoires et les applications qui dépendent de plusieurs bibliothèques. Il est utilisé en conjonction avec des environnements de construction natifs tels que make, Xcode d'Apple et Microsoft Visual Studio.

Avec CMake, vous n'avez plus besoin de conserver des paramètres séparés spécifiques à votre environnement de compilation / build. Vous avez une configuration, et cela fonctionne pour de nombreux environnements.

CMake peut générer une solution Microsoft Visual Studio, un projet Eclipse ou un labyrinthe Makefile à partir des mêmes fichiers sans rien y changer.

Étant donné un tas de répertoires contenant du code, CMake gère toutes les dépendances, les commandes de construction et d'autres tâches dont votre projet a besoin avant de pouvoir être compilé. Il ne compile rien en fait. Pour utiliser CMake, vous devez lui indiquer (en utilisant des fichiers de configuration appelés CMakeLists.txt) quels exécutables vous avez besoin de compiler, à quelles bibliothèques ils sont liés, quels répertoires se trouvent dans votre projet et ce qu'ils contiennent, ainsi que tous les détails tels que les indicateurs ou tout ce dont vous avez besoin (CMake est assez puissant).

Si cela est correctement configuré, vous utilisez alors CMake pour créer tous les fichiers dont votre «environnement de construction natif» de choix a besoin pour faire son travail. Sous Linux, par défaut, cela signifie Makefiles. Donc, une fois que vous exécutez CMake, il créera un tas de fichiers pour son propre usage plus quelques Makefiles. Tout ce que vous avez à faire par la suite est de taper "make" dans la console à partir du dossier racine chaque fois que vous avez fini de modifier votre code, et bam, un exécutable compilé et lié est créé.

Comment fonctionne CMake? Qu'est ce que ça fait?

Voici un exemple de configuration de projet que je vais utiliser tout au long:

simple/
  CMakeLists.txt
  src/
    tutorial.cxx
    CMakeLists.txt
  lib/
    TestLib.cxx
    TestLib.h
    CMakeLists.txt
  build/

Le contenu de chaque fichier est affiché et discuté plus tard.

CMake configure votre projet en fonction de la racine CMakeLists.txt de votre projet, et le fait dans le répertoire que vous avez exécuté cmakedans la console. Faire cela à partir d'un dossier qui n'est pas la racine de votre projet produit ce qu'on appelle une construction hors source , ce qui signifie que les fichiers créés lors de la compilation (fichiers obj, fichiers lib, exécutables, vous savez) seront placés dans ledit dossier , séparés du code réel. Il aide à réduire l'encombrement et est également préféré pour d'autres raisons, dont je ne parlerai pas.

Je ne sais pas ce qui se passe si vous exécutez cmakesur un autre que la racine CMakeLists.txt.

Dans cet exemple, puisque je veux que tout soit placé dans le build/dossier, je dois d'abord y naviguer, puis passer à CMake le répertoire dans lequel CMakeLists.txtréside la racine .

cd build
cmake ..

Par défaut, cela configure tout en utilisant Makefiles comme je l'ai dit. Voici à quoi devrait ressembler le dossier de construction maintenant:

simple/build/
  CMakeCache.txt
  cmake_install.cmake
  Makefile
  CMakeFiles/
    (...)
  src/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile
  lib/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile

Quels sont tous ces fichiers? La seule chose dont vous devez vous soucier est le Makefile et les dossiers du projet .

Notez les dossiers src/et lib/. Celles-ci ont été créées car simple/CMakeLists.txtelles les désignent à l'aide de la commande add_subdirectory(<folder>). Cette commande indique à CMake de rechercher dans ledit dossier un autre CMakeLists.txtfichier et d'exécuter ce script, de sorte que chaque sous-répertoire ajouté de cette manière doit contenir un CMakeLists.txtfichier. Dans ce projet, simple/src/CMakeLists.txtdécrit comment créer l'exécutable réel et simple/lib/CMakeLists.txtcomment créer la bibliothèque. Chaque cible CMakeLists.txtdécrite par a sera placée par défaut dans son sous-répertoire dans l'arborescence de construction. Alors, après un rapide

make

dans la console done from build/, certains fichiers sont ajoutés:

simple/build/
  (...)
  lib/
    libTestLib.a
    (...)
  src/
    Tutorial
    (...)

Le projet est construit et l'exécutable est prêt à être exécuté. Que faites-vous si vous voulez que les exécutables soient placés dans un dossier spécifique? Définissez la variable CMake appropriée ou modifiez les propriétés d'une cible spécifique . Plus d'informations sur les variables CMake plus tard.

Comment dire à CMake comment créer mon projet?

Voici le contenu, expliqué, de chaque fichier dans le répertoire source:

simple/CMakeLists.txt:

cmake_minimum_required(VERSION 2.6)

project(Tutorial)

# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)

La version minimale requise doit toujours être définie, conformément à l'avertissement que CMake lance lorsque vous ne le faites pas. Utilisez quelle que soit votre version de CMake.

Le nom de votre projet peut être utilisé plus tard, et indique que vous pouvez gérer plus d'un projet à partir des mêmes fichiers CMake. Je ne vais pas approfondir cela, cependant.

Comme mentionné précédemment, add_subdirectory()ajoute un dossier au projet, ce qui signifie que CMake s'attend à ce qu'il ait un dossier CMakeLists.txt, qu'il exécutera ensuite avant de continuer. Au fait, si vous avez défini une fonction CMake, vous pouvez l'utiliser à partir d'autres CMakeLists.txtsous-répertoires, mais vous devez la définir avant de l'utiliser add_subdirectory()ou elle ne la trouvera pas. Cependant, CMake est plus intelligent sur les bibliothèques, c'est donc probablement la seule fois où vous rencontrerez ce genre de problème.

simple/lib/CMakeLists.txt:

add_library(TestLib TestLib.cxx)

Pour créer votre propre bibliothèque, donnez-lui un nom, puis listez tous les fichiers à partir desquels elle est construite. Simple. S'il fallait un autre fichier, foo.cxxà compiler, vous écririez à la place add_library(TestLib TestLib.cxx foo.cxx). Cela fonctionne également pour les fichiers dans d'autres répertoires, par exemple add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx). Plus d'informations sur la variable CMAKE_SOURCE_DIR ultérieurement.

Une autre chose que vous pouvez faire avec cela est de spécifier que vous voulez une bibliothèque partagée. L'exemple: add_library(TestLib SHARED TestLib.cxx). N'ayez crainte, c'est là que CMake commence à vous faciliter la vie. Qu'elle soit partagée ou non, tout ce dont vous avez besoin pour utiliser une bibliothèque créée de cette manière est le nom que vous lui avez donné ici. Le nom de cette bibliothèque est maintenant TestLib et vous pouvez la référencer de n'importe où dans le projet. CMake le trouvera.

Existe-t-il un meilleur moyen de répertorier les dépendances? Certainement oui . Vérifiez ci-dessous pour plus d'informations à ce sujet.

simple/lib/TestLib.cxx:

#include <stdio.h>

void test() {
  printf("testing...\n");
}

simple/lib/TestLib.h:

#ifndef TestLib
#define TestLib

void test();

#endif

simple/src/CMakeLists.txt:

# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)

# Link to needed libraries
target_link_libraries(Tutorial TestLib)

# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)

La commande add_executable()fonctionne exactement de la même manière que add_library(), sauf, bien sûr, qu'elle générera un exécutable à la place. Cet exécutable peut maintenant être référencé en tant que cible pour des choses comme target_link_libraries(). Étant donné que tutorial.cxx utilise le code trouvé dans la bibliothèque TestLib, vous le signalez à CMake comme indiqué.

De même, tous les fichiers .h #inclus par des sources dans add_executable()qui ne sont pas dans le même répertoire que la source doivent être ajoutés d'une manière ou d'une autre. Si ce n'est pas pour la target_include_directories()commande, lib/TestLib.hne sera pas trouvé lors de la compilation du didacticiel, donc le lib/dossier entier est ajouté aux répertoires d'inclusion à rechercher pour #includes. Vous pouvez également voir la commande include_directories()qui agit de la même manière, sauf qu'elle n'a pas besoin de spécifier une cible car elle la définit globalement, pour tous les exécutables. Encore une fois, j'expliquerai CMAKE_SOURCE_DIR plus tard.

simple/src/tutorial.cxx:

#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
  test();
  fprintf(stdout, "Main\n");
  return 0;
}

Notez comment le fichier "TestLib.h" est inclus. Pas besoin d'inclure le chemin complet: CMake s'occupe de tout cela en coulisse grâce à target_include_directories().

Techniquement parlant, dans un simple arbre source comme celui-ci, vous pouvez vous passer du CMakeLists.txts sous lib/et src/et simplement ajouter quelque chose comme add_executable(Tutorial src/tutorial.cxx)à simple/CMakeLists.txt. Cela dépend de vous et des besoins de votre projet.

Que dois-je savoir d'autre pour utiliser correctement CMake?

(Sujets AKA pertinents pour votre compréhension)

Trouver et utiliser des packages : la réponse à cette question l' explique mieux que je ne le pourrais jamais.

Déclarer des variables et des fonctions, utiliser le flux de contrôle, etc .: consultez ce tutoriel qui explique les bases de ce que CMake a à offrir, ainsi qu'une bonne introduction en général.

Variables CMake : il y en a beaucoup, donc ce qui suit est un cours intensif pour vous mettre sur la bonne voie. Le wiki CMake est un bon endroit pour obtenir des informations plus détaillées sur les variables et ostensiblement d'autres choses.

Vous souhaiterez peut-être modifier certaines variables sans reconstruire l'arborescence de construction. Utilisez ccmake pour cela (il édite le CMakeCache.txtfichier). N'oubliez pas de cconfigurer une fois les modifications effectuées, puis d' générer les fichiers makefiles avec la configuration mise à jour.

Lisez le didacticiel référencé précédemment pour en savoir plus sur l'utilisation des variables, mais pour faire court: set(<variable name> value)pour modifier ou créer une variable. ${<variable name>}pour l'utiliser.

  • CMAKE_SOURCE_DIR: Le répertoire racine de la source. Dans l'exemple précédent, c'est toujours égal à/simple
  • CMAKE_BINARY_DIR: Le répertoire racine de la construction. Dans l'exemple précédent, cela équivaut à simple/build/, mais si vous exécutez à cmake simple/partir d'un dossier tel que foo/bar/etc/, toutes les références à CMAKE_BINARY_DIRdans cet arbre de construction deviendraient /foo/bar/etc.
  • CMAKE_CURRENT_SOURCE_DIR: Le répertoire dans lequel se trouve le courant CMakeLists.txt. Cela signifie qu'il change d'un bout à l'autre: l'impression à partir des simple/CMakeLists.txtrendements /simpleet l'impression à partir des simple/src/CMakeLists.txtrendements /simple/src.
  • CMAKE_CURRENT_BINARY_DIR: Vous avez eu l'idée. Ce chemin dépendra non seulement du dossier dans lequel se trouve la construction, mais également de l' CMakeLists.txtemplacement du script actuel .

Pourquoi sont-ils importants? Les fichiers sources ne seront évidemment pas dans l'arborescence de construction. Si vous essayez quelque chose comme target_include_directories(Tutorial PUBLIC ../lib)dans l'exemple précédent, ce chemin sera relatif à l'arbre de construction, c'est-à-dire que ce sera comme l'écriture ${CMAKE_BINARY_DIR}/lib, qui regardera à l'intérieur simple/build/lib/. Il n'y a pas de fichiers .h là-dedans; tout au plus vous trouverez libTestLib.a. Vous voulez à la ${CMAKE_SOURCE_DIR}/libplace.

  • CMAKE_CXX_FLAGS: Indicateurs à transmettre au compilateur, dans ce cas le compilateur C ++. Il convient également de noter CMAKE_CXX_FLAGS_DEBUGqui sera utilisé à la place si CMAKE_BUILD_TYPEest défini sur DEBUG. Il y en a plus comme ceux-ci; consultez le wiki CMake .
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY: Indiquez à CMake où placer tous les exécutables une fois construits. Il s'agit d'un paramètre global. Vous pouvez, par exemple, le régler sur bin/et tout y placer proprement. EXECUTABLE_OUTPUT_PATHest similaire, mais obsolète, au cas où vous tomberiez dessus.
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY: De même, un paramètre global pour indiquer à CMake où placer tous les fichiers de bibliothèque.

Propriétés de la cible : vous pouvez définir des propriétés qui n'affectent qu'une seule cible, que ce soit un exécutable ou une bibliothèque (ou une archive ... vous voyez l'idée). Voici un bon exemple de son utilisation (avec set_target_properties().

Existe-t-il un moyen simple d'ajouter automatiquement des sources à une cible? Utilisez GLOB pour lister tout dans un répertoire donné sous la même variable. Un exemple de syntaxe est FILE(GLOB <variable name> <directory>/*.cxx).

Pouvez-vous spécifier différents types de build? Oui, même si je ne suis pas sûr de savoir comment cela fonctionne ou de ses limites. Cela nécessite probablement un certain if / then'ning, mais CMake offre un support de base sans rien configurer, comme les valeurs par défaut pour le CMAKE_CXX_FLAGS_DEBUG, par exemple. Vous pouvez définir votre type de construction à partir du CMakeLists.txtfichier via set(CMAKE_BUILD_TYPE <type>)ou en appelant CMake depuis la console avec les indicateurs appropriés, par exemple cmake -DCMAKE_BUILD_TYPE=Debug.

Avez-vous de bons exemples de projets utilisant CMake? Wikipédia a une liste de projets open source qui utilisent CMake, si vous voulez vous pencher là-dessus. Les didacticiels en ligne ne m'ont jusqu'ici été qu'une déception à cet égard, mais cette question de Stack Overflow a une configuration CMake assez cool et facile à comprendre. Ça vaut le coup d'œil.

Utilisation de variables de CMake dans votre code : Voici un exemple rapide et sale (adapté d' un autre tutoriel ):

simple/CMakeLists.txt:

project (Tutorial)

# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)

# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)

simple/TutorialConfig.h.in:

// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

Le fichier résultant généré par CMake, simple/src/TutorialConfig.h:

// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1

Avec une utilisation intelligente de ceux-ci, vous pouvez faire des choses intéressantes comme éteindre une bibliothèque, etc. Je recommande de jeter un coup d'œil à ce tutoriel car il y a des choses légèrement plus avancées qui seront sûrement très utiles sur des projets plus importants, tôt ou tard.

Pour tout le reste, Stack Overflow regorge de questions spécifiques et de réponses concises, ce qui est idéal pour tout le monde sauf les non-initiés.

leinaD_natipaC
la source
2
J'accepte cette réponse, mais si quelqu'un d'autre écrit une meilleure réponse plus courte, je la choisirai. Je le ferais moi-même mais j'ai déjà assez souffert de CMake.
leinaD_natipaC
6
Merci pour ce travail incroyable. J'espère que cela suscitera plus de votes! :)
LETs
4
Le point plutôt sous-estimé là-dedans: avec CMake, vous n'avez plus besoin de maintenir des paramètres séparés spécifiques à votre environnement de compilation / build. Vous avez une configuration, et cela fonctionne pour (différentes versions de) Visual Studio, les blocs de code, les Makefiles Unix simples et de nombreux autres environnements. C'est pourquoi je me suis intéressé à CMake en premier lieu: il est devenu très difficile de synchroniser les configurations Autoconf, MSVC 2005 / 32bit et MSVC 2010 / 64bit. Et une fois que j'ai compris, j'ai ajouté beaucoup plus de choses comme la génération de documents, les tests, le packaging, etc., tous de manière multiplateforme.
DevSolar
1
@DevSolar True. J'aime cette ligne plus que celle de wikipedia, alors je l'ajouterai à la réponse si cela ne vous dérange pas. Je ne rentrerai pas dans quoi que ce soit sur la façon d'utiliser correctement CMake à cet effet. Il s'agit plus simplement de pouvoir dire ce qui se passe.
leinaD_natipaC
1
@RichieHH En suivant l'exemple, vous configurez d'abord CMake pour utiliser Makefiles, puis vous utilisez CMake pour générer les fichiers dont votre projet a besoin d'être construit, et enfin vous arrêtez de vous soucier de CMake car son travail ici est terminé. Donc vous vous «inquiétez» du Makefile dans le sens où vous devez désormais l'utiliser makedans bash pour, eh bien, faire votre projet.
leinaD_natipaC