À 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 Makefile
s. 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é cmake
dans 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 cmake
sur 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.txt
ré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.txt
elles les désignent à l'aide de la commande add_subdirectory(<folder>)
. Cette commande indique à CMake de rechercher dans ledit dossier un autre CMakeLists.txt
fichier et d'exécuter ce script, de sorte que chaque sous-répertoire ajouté de cette manière doit contenir un CMakeLists.txt
fichier. Dans ce projet, simple/src/CMakeLists.txt
décrit comment créer l'exécutable réel et simple/lib/CMakeLists.txt
comment créer la bibliothèque. Chaque cible CMakeLists.txt
dé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.txt
sous-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.h
ne 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.txt
s 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.txt
fichier). N'oubliez pas de c
configurer 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_DIR
dans 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.txt
rendements /simple
et l'impression à partir des simple/src/CMakeLists.txt
rendements /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.txt
emplacement 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}/lib
place.
CMAKE_CXX_FLAGS
: Indicateurs à transmettre au compilateur, dans ce cas le compilateur C ++. Il convient également de noter CMAKE_CXX_FLAGS_DEBUG
qui sera utilisé à la place si CMAKE_BUILD_TYPE
est 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_PATH
est 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.txt
fichier 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.