Guide CMake complet. Deuxième partie: Build System


Présentation


Cet article décrit l'utilisation du système de génération CMake utilisé dans un grand nombre de projets C / C ++. Il est fortement recommandé de lire la première partie du manuel pour éviter de mal comprendre la syntaxe du langage CMake, qui apparaît explicitement dans tout l'article.


Lancement de CMake


Voici des exemples d'utilisation du langage CMake que vous devez pratiquer. Expérimentez avec le code source en modifiant les commandes existantes et en en ajoutant de nouvelles. Pour exécuter ces exemples, installez CMake à partir du site Web officiel .


Principe de fonctionnement


Le système de construction CMake est un wrapper sur d'autres utilitaires dépendant de la plate-forme (par exemple, Ninja ou Make ). Ainsi, dans le processus d'assemblage lui-même, aussi paradoxal que cela puisse paraître, il ne participe pas directement.


Le système de build CMake accepte un fichier CMakeLists.txt avec une description des règles de build dans le langage CMake formel, puis génère des fichiers de build natifs et intermédiaires dans le même répertoire accepté sur votre plateforme.


Les fichiers générés contiendront des noms spécifiques d'utilitaires système, de répertoires et de compilateurs, tandis que les commandes CMake n'utilisent que le concept abstrait du compilateur et ne sont pas liées à des outils dépendants de la plate-forme qui diffèrent considérablement selon les différents systèmes d'exploitation.


Vérification de la version de CMake


La commande cmake_minimum_required vérifie la version en cours d'exécution de CMake: si elle est inférieure au minimum spécifié, alors CMake se termine avec une erreur fatale. Un exemple qui montre l'utilisation typique de cette commande au début de n'importe quel fichier CMake:


 #     CMake: cmake_minimum_required(VERSION 3.0) 

Comme indiqué dans les commentaires, la commande cmake_minimum_required définit tous les indicateurs de compatibilité (voir cmake_policy ). Certains développeurs ont intentionnellement défini une version basse de CMake, puis ajustent la fonctionnalité manuellement. Cela vous permet de prendre en charge simultanément les anciennes versions de CMake et, à certains endroits, de profiter de nouvelles fonctionnalités.


Conception du projet


Au début de tout CMakeLists.txt spécifier les caractéristiques du projet avec l'équipe de projet pour une meilleure conception avec des environnements intégrés et d'autres outils de développement.


 #    "MyProject": project(MyProject VERSION 1.2.3.4 LANGUAGES C CXX) 

Il convient de noter que si le mot clé LANGUAGES est omis, les langues par défaut sont C CXX . Vous pouvez également désactiver l'indication de toutes les langues en écrivant le mot-clé NONE sous forme de liste de langues ou simplement laisser une liste vide.


Exécution de fichiers de script


La commande include remplace la ligne de son appel par le code du fichier spécifié, agissant de manière similaire à la commande include préprocesseur C / C ++. Cet exemple exécute le fichier de script MyCMakeScript.cmake commande décrite:


 message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]") #   `MyCMakeScript.cmake`  : include(MyCMakeScript.cmake) message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]") 

Dans cet exemple, le premier message notifiera que la variable TEST_VARIABLE pas encore été définie, cependant, si le script MyCMakeScript.cmake cette variable, le deuxième message informera déjà de la nouvelle valeur de la variable de test. Ainsi, le fichier de script inclus par la commande include ne crée pas sa propre portée, qui était mentionnée dans les commentaires de l' article précédent .


Compilation de fichiers exécutables


La commande add_executable compile le fichier exécutable avec le nom donné dans la liste source. Il est important de noter que le nom de fichier final dépend de la plate-forme cible (par exemple, <ExecutableName>.exe ou simplement <ExecutableName> ). Un exemple typique d'appeler cette commande:


 #    "MyExecutable"  #  "ObjectHandler.c", "TimeManager.c"  "MessageGenerator.c": add_executable(MyExecutable ObjectHandler.c TimeManager.c MessageGenerator.c) 

Compilation de bibliothèque


La commande add_library compile la bibliothèque avec la vue et le nom spécifiés à partir de la source. Il est important de noter que le nom de bibliothèque final dépend de la plate-forme cible (par exemple, lib<LibraryName>.a ou <LibraryName>.lib ). Un exemple typique d'appeler cette commande:


 #    "MyLibrary"  #  "ObjectHandler.c", "TimeManager.c"  "MessageConsumer.c": add_library(MyLibrary STATIC ObjectHandler.c TimeManager.c MessageConsumer.c) 

  • Les bibliothèques statiques sont spécifiées par le mot clé STATIC comme deuxième argument et sont des archives de fichiers objets associés à des fichiers exécutables et à d'autres bibliothèques au moment de la compilation;
  • Les bibliothèques dynamiques sont spécifiées par le mot clé SHARED comme deuxième argument et sont des bibliothèques binaires chargées par le système d'exploitation lors de l'exécution du programme;
  • Les bibliothèques modulaires sont définies par le mot-clé MODULE comme deuxième argument et sont des bibliothèques binaires chargées en utilisant la technique d'exécution par l'exécutable lui-même;
  • Les bibliothèques d'objets sont définies par le mot clé OBJECT comme deuxième argument et sont un ensemble de fichiers objets associés à des fichiers exécutables et à d'autres bibliothèques au moment de la compilation.

Ajout d'une source à l'objectif


Il existe des cas qui nécessitent plusieurs ajouts de fichiers source à la cible. Pour ce faire, la commande target_sources est target_sources , qui peut ajouter plusieurs fois des sources à la cible.


Le premier argument de la commande target_sources est le nom de la cible précédemment spécifié à l'aide des add_executable add_library ou add_executable , et les arguments suivants sont une liste des fichiers source à ajouter.


Les appels répétés à la target_sources ajoutent les fichiers source à la cible dans l'ordre dans lequel ils ont été appelés, de sorte que les deux derniers blocs de code sont fonctionnellement équivalents:


 #    "MyExecutable"   # "ObjectPrinter.c"  "SystemEvaluator.c": add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c) #    "MyExecutable"  "MessageConsumer.c": target_sources(MyExecutable MessageConsumer.c) #    "MyExecutable"  "ResultHandler.c": target_sources(MyExecutable ResultHandler.c) 

 #    "MyExecutable"   # "ObjectPrinter.c", "SystemEvaluator.c", "MessageConsumer.c"  "ResultHandler.c": add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c MessageConsumer.c ResultHandler.c) 

Fichiers générés


L'emplacement des fichiers de sortie générés par les add_library add_executable et add_library n'est déterminé qu'au stade de la génération, cependant, cette règle peut être modifiée avec plusieurs variables qui déterminent l'emplacement final des fichiers binaires:



Les fichiers exécutables sont toujours considérés comme des objectifs d'exécution, les bibliothèques statiques sont considérées comme des objectifs d'archivage et les bibliothèques modulaires sont considérées comme des objectifs de bibliothèque. Pour les plates-formes "non DLL", les bibliothèques dynamiques sont considérées comme des cibles de bibliothèque et pour les "plates-formes DLL", des objectifs d'exécution. Ces variables ne sont pas fournies pour les bibliothèques d'objets, car ce type de bibliothèques est généré dans les entrailles du répertoire CMakeFiles .


Il est important de noter que toutes les plates-formes Windows, y compris Cygwin, sont considérées comme des "plates-formes DLL".


Disposition de la bibliothèque


La commande target_link_libraries bibliothèque ou un exécutable avec d'autres bibliothèques fournies. Le premier argument de cette commande est le nom de la cible générée par les add_library add_executable ou add_library , et les arguments suivants sont les noms des cibles de bibliothèque ou les chemins d'accès complets aux bibliothèques. Un exemple:


 #    "MyExecutable"  #  "JsonParser", "SocketFactory"  "BrowserInvoker": target_link_libraries(MyExecutable JsonParser SocketFactory BrowserInvoker) 

Il convient de noter que les bibliothèques modulaires ne peuvent pas être liées à des fichiers exécutables ou à d'autres bibliothèques, car elles sont uniquement destinées au chargement par des techniques d'exécution.


Travailler avec des objectifs


Comme mentionné dans les commentaires, les cibles dans CMake sont également sujettes à une manipulation manuelle, mais très limitée.


Il est possible de contrôler les propriétés des cibles conçues pour définir le processus d'assemblage du projet. La commande get_target_property valeur de la propriété cible sur la variable fournie. Cet exemple affiche la valeur de la propriété C_STANDARD de la cible C_STANDARD à l'écran:


 #   "VALUE"   "C_STANDARD": get_target_property(VALUE MyTarget C_STANDARD) #      : message("'C_STANDARD' property is equal to [${VALUE}]") 

La commande set_target_properties définit les propriétés cibles spécifiées sur les valeurs spécifiées. Cette commande accepte une liste d'objectifs pour lesquels des valeurs de propriété seront définies, puis le mot-clé PROPERTIES , suivi d'une liste de la forme < > < > :


 #   'C_STANDARD'  "11", #   'C_STANDARD_REQUIRED'  "ON": set_target_properties(MyTarget PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON) 

L'exemple ci-dessus définit les propriétés des cibles MyTarget qui affectent le processus de compilation, à savoir: lors de la compilation de la cible MyTarget CMake MyTarget compilateur d'utiliser la norme C11. Tous les noms de propriété cible connus sont répertoriés sur cette page .


Il est également possible de vérifier les cibles précédemment définies à l'aide de la construction if(TARGET <TargetName>) :


 #  "The target was defined!"   "MyTarget"  , #    "The target was not defined!": if(TARGET MyTarget) message("The target was defined!") else() message("The target was not defined!") endif() 

Ajout de sous-projets


La commande add_subdirectory invite CMake à traiter immédiatement le fichier de sous-projet spécifié. L'exemple ci-dessous illustre l'application du mécanisme décrit:


 #   "subLibrary"    , #       "subLibrary/build": add_subdirectory(subLibrary subLibrary/build) 

Dans cet exemple, le premier argument de la commande add_subdirectory est le sous-projet add_subdirectory et le deuxième argument est facultatif et informe CMake du dossier destiné aux fichiers générés du sous-projet inclus (par exemple, CMakeCache.txt et cmake_install.cmake ).


Il convient de noter que toutes les variables de la portée parent sont héritées par le répertoire ajouté, et toutes les variables définies et redéfinies dans ce répertoire ne seront visibles que par lui (si le mot-clé PARENT_SCOPE pas PARENT_SCOPE spécifié par l'argument de commande set ). Cette fonctionnalité a été mentionnée dans les commentaires de l' article précédent .


Recherche de package


La commande find_package recherche et charge les paramètres d'un projet externe. Dans la plupart des cas, il est utilisé pour la liaison ultérieure de bibliothèques externes telles que Boost et GSL . Cet exemple appelle la commande décrite pour rechercher la bibliothèque GSL puis lier:


 #     "GSL": find_package(GSL 2.5 REQUIRED) #      "GSL": target_link_libraries(MyExecutable GSL::gsl) #      "GSL": target_include_directories(MyExecutable ${GSL_INCLUDE_DIRS}) 

Dans l'exemple ci-dessus, la commande find_package accepte le nom du package comme premier argument, puis la version requise. L'option REQUIRED nécessite l'impression d'une erreur fatale et la fermeture de CMake si le package requis n'est pas trouvé. L'opposé est l'option QUIET , obligeant CMake à continuer son travail, même si le paquet n'a pas été trouvé.


Ensuite, l' MyExecutable lié à la bibliothèque GSL avec la commande target_link_libraries à l'aide de la variable GSL::gsl , qui encapsule l'emplacement du GSL déjà compilé.


À la fin, la commande target_include_directories est target_include_directories , informant le compilateur de l'emplacement des fichiers d'en-tête de bibliothèque GSL. Veuillez noter que la variable GSL_INCLUDE_DIRS est utilisée pour GSL_INCLUDE_DIRS emplacement des en-têtes que j'ai décrits (il s'agit d'un exemple de paramètres de package importés).


Vous souhaiterez probablement vérifier le résultat d'une recherche de package si vous avez spécifié l'option QUIET . Cela peut être fait en vérifiant la <PackageName>_FOUND , qui est automatiquement déterminée une fois la commande find_package . Par exemple, si vous importez avec succès les paramètres GSL dans votre projet, la variable GSL_FOUND deviendra vraie.


En général, la commande find_package a deux find_package de lancement: modulaire et configuration. L'exemple ci-dessus a appliqué une forme modulaire. Cela signifie que lorsque la commande est appelée, CMake recherche un fichier de script du formulaire Find<PackageName>.cmake dans le répertoire CMAKE_MODULE_PATH , puis le lance et importe tous les paramètres nécessaires (dans ce cas, CMake a lancé le fichier FindGSL.cmake standard).


Façons d'inclure des en-têtes


Vous pouvez informer le compilateur de l'emplacement des en-têtes inclus en utilisant deux commandes: include_directories et target_include_directories . Vous décidez lequel utiliser, cependant, il convient de considérer certaines différences entre eux (l'idée est suggérée dans les commentaires ).


La commande include_directories affecte la portée du répertoire. Cela signifie que tous les répertoires d'en-tête spécifiés par cette commande seront utilisés pour toutes les fins du CMakeLists.txt actuel, ainsi que pour les sous-projets traités (voir add_subdirectory ).


La commande target_include_directories affecte target_include_directories la cible spécifiée par le premier argument et n'affecte pas les autres cibles. L'exemple ci-dessous montre la différence entre les deux commandes:


 add_executable(RequestGenerator RequestGenerator.c) add_executable(ResponseGenerator ResponseGenerator.c) #     "RequestGenerator": target_include_directories(RequestGenerator headers/specific) #    "RequestGenerator"  "ResponseGenerator": include_directories(headers) 

Dans les commentaires, il est mentionné que dans les projets modernes, l'utilisation des link_libraries include_directories et link_libraries n'est pas souhaitable. Une alternative est les target_link_libraries target_include_directories et target_link_libraries qui n'agissent que sur des objectifs spécifiques, et non sur l'ensemble de la portée actuelle.


Installation du projet


La commande install génère des règles d'installation pour votre projet. Cette commande est capable de travailler avec des objectifs, des fichiers, des dossiers, etc. Tout d'abord, envisagez de fixer des objectifs.


Pour définir des objectifs, vous devez passer le mot clé TARGETS comme premier argument de la fonction décrite, suivi d'une liste des objectifs à définir, puis du mot clé DESTINATION avec l'emplacement du répertoire dans lequel les objectifs spécifiés seront définis. Cet exemple illustre une définition d'objectif typique:


 #   "TimePrinter"  "DataScanner"   "bin": install(TARGETS TimePrinter DataScanner DESTINATION bin) 

Le processus de description de l'installation des fichiers est similaire, sauf que TARGETS devez spécifier FILES au lieu du mot clé TARGETS . Un exemple démontrant l'installation de fichiers:


 #   "DataCache.txt"  "MessageLog.txt"   "~/": install(FILES DataCache.txt MessageLog.txt DESTINATION ~/) 

Le processus de description de l'installation des dossiers est similaire, sauf que vous devez spécifier DIRECTORY au lieu du mot-clé FILES . Il est important de noter que lors de l'installation, tout le contenu du dossier sera copié, et pas seulement son nom. Un exemple d'installation de dossiers est le suivant:


 #   "MessageCollection"  "CoreFiles"   "~/": install(DIRECTORY MessageCollection CoreFiles DESTINATION ~/) 

Après avoir terminé le traitement CMake de tous vos fichiers, vous pouvez installer tous les objets décrits avec la sudo checkinstall (si CMake génère un Makefile ), ou effectuer cette action avec l'environnement de développement intégré qui prend en charge CMake.


Exemple visuel du projet


Ce guide ne serait pas complet sans démontrer un exemple concret d'utilisation du système de construction CMake. Considérons un diagramme de projet simple utilisant CMake comme seul système de construction:


 + MyProject - CMakeLists.txt - Defines.h - StartProgram.c + core - CMakeLists.txt - Core.h - ProcessInvoker.c - SystemManager.c 

Le fichier d'assemblage principal CMakeLists.txt décrit la compilation de l'ensemble du programme: tout d'abord, la commande add_executable est add_executable qui compile le fichier exécutable, puis la commande add_subdirectory est add_subdirectory , ce qui stimule le traitement du sous-projet, et enfin, le fichier exécutable est lié à la bibliothèque compilée:


 #    CMake: cmake_minimum_required(VERSION 3.0) #   : project(MyProgram VERSION 1.0.0 LANGUAGES C) #      "MyProgram": add_executable(MyProgram StartProgram.c) #    "core/CMakeFiles.txt": add_subdirectory(core) #    "MyProgram"  #    "MyProgramCore": target_link_libraries(MyProgram MyProgramCore) #    "MyProgram"   "bin": install(TARGETS MyProgram DESTINATION bin) 

Le fichier core/CMakeLists.txt est appelé par le fichier d'assembly principal et compile la bibliothèque statique MyProgramCore destinée à la liaison avec le fichier exécutable:


 #    CMake: cmake_minimum_required(VERSION 3.0) #      "MyProgramCore": add_library(MyProgramCore STATIC ProcessInvoker.c SystemManager.c) 

Après une série de commandes cmake . && make && sudo checkinstall cmake . && make && sudo checkinstall système de build CMake se termine avec succès. La première commande commence à traiter le fichier CMakeLists.txt dans le répertoire racine du projet, la deuxième commande compile enfin les fichiers binaires nécessaires et la troisième commande installe l' MyProgram compilé MyProgram dans le système.


Conclusion


Vous pouvez maintenant écrire les vôtres et comprendre les fichiers CMake d'autres personnes, et vous pouvez lire en détail d'autres mécanismes sur le site officiel .


Le prochain article de ce guide se concentrera sur les tests et la création de packages à l'aide de CMake et sera publié dans une semaine. A très bientôt!

Source: https://habr.com/ru/post/fr432096/


All Articles