Pendant le développement, j'aime changer de compilateur, de mode de construction, de version de dépendance, effectuer une analyse statique, mesurer les performances, collecter la couverture, générer de la documentation, etc. Et j'aime vraiment CMake car il me permet de faire tout ce que je veux.
Beaucoup de gens critiquent CMake, et souvent à juste titre, mais si vous y regardez, tout n'est pas si mauvais, et récemment pas mal du tout, et la direction du développement est assez positive.
Dans cette note, je souhaite vous expliquer comment organiser simplement une bibliothèque d'en-têtes en C++ dans le système CMake pour obtenir les fonctionnalités suivantes :
Nous parlerons principalement de la façon d'organiser les scripts CMake, ils seront donc discutés en détail. Tout le monde peut voir le reste des fichiers directement sur la page du projet modèle.
Tout d'abord, vous devez demander la version requise du système CMake. CMake évolue, les signatures de commandes et le comportement dans différentes conditions changent. Pour que CMake comprenne immédiatement ce que nous attendons de lui, nous devons immédiatement enregistrer nos exigences à ce sujet.
cmake_minimum_required(VERSION 3.13)
Ensuite nous désignerons notre projet, son nom, sa version, les langues utilisées, etc. (voir. команду project).
Dans ce cas nous indiquons la langue CXX (et cela signifie C++) afin que CMake ne se fatigue pas et ne recherche pas un compilateur de langage C (par défaut, CMake inclut deux langages : C et C++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Ici, vous pouvez vérifier immédiatement si notre projet est inclus dans un autre projet en tant que sous-projet. Cela aidera beaucoup à l’avenir.
La première option est MYLIB_TESTING — pour désactiver les tests unitaires. Cela peut être nécessaire si nous sommes sûrs que tout est en ordre avec les tests, mais que nous souhaitons uniquement, par exemple, installer ou packager notre projet. Soit notre projet est inclus en tant que sous-projet - dans ce cas, l'utilisateur de notre projet n'est pas intéressé à exécuter nos tests. Vous ne testez pas les dépendances que vous utilisez, n'est-ce pas ?
De plus, nous ferons une option distincte MYLIB_COVERAGE pour mesurer la couverture du code par les tests, mais cela nécessitera des outils supplémentaires, il devra donc être activé explicitement.
Bien sûr, nous sommes des programmeurs sympas et nous voulons donc le niveau maximum de diagnostics au moment de la compilation de la part du compilateur. Pas une seule souris ne passera à travers.
Notre bibliothèque se compose uniquement de fichiers d'en-tête, ce qui signifie que nous n'avons aucun épuisement sous forme de bibliothèques statiques ou dynamiques. Par contre, pour pouvoir utiliser notre bibliothèque en externe, il faut qu'elle soit installée, qu'elle soit détectable dans le système et connectée à votre projet, et en même temps ces mêmes en-têtes, ainsi qu'éventuellement quelques supplémentaires, lui sont attachés des propriétés.
Pour cela, nous créons une bibliothèque d'interfaces.
add_library(mylib INTERFACE)
Nous lions les en-têtes à notre bibliothèque d'interface.
L'utilisation moderne, à la mode et par les jeunes de CMake implique que les en-têtes, les propriétés, etc. transmis via une seule cible. Alors il suffit de dire target_link_libraries(target PRIVATE dependency), et tous les en-têtes associés à la cible dependency, sera disponible pour les sources appartenant à la cible target. Et tu n'en as pas besoin [target_]include_directories. Cela sera démontré ci-dessous dans l’analyse Script CMake pour les tests unitaires.
Cette commande associe les en-têtes dont nous avons besoin à notre bibliothèque d'interface, et si notre bibliothèque est connectée à une cible dans la même hiérarchie CMake, alors les en-têtes du répertoire lui seront associés. ${CMAKE_CURRENT_SOURCE_DIR}/include, et si notre bibliothèque est installée sur le système et connectée à un autre projet à l'aide de la commande find_package, alors les en-têtes du répertoire lui seront associés include par rapport au répertoire d'installation.
Établissons une norme linguistique. Bien sûr, le tout dernier. Dans le même temps, nous incluons non seulement la norme, mais nous l'étendons également à ceux qui utiliseront notre bibliothèque. Ceci est obtenu grâce au fait que la propriété définie a une catégorie INTERFACE (Voir. commande target_compile_features).
Créons un alias pour notre bibliothèque. De plus, pour la beauté, ce sera dans un « espace de noms » spécial. Cela sera utile lorsque différents modules apparaîtront dans notre bibliothèque et que nous allons les connecter indépendamment les uns des autres. Comme à Busta par exemple.
Installation de nos en-têtes dans le système. Tout est simple ici. On dit que le dossier avec tous les en-têtes doit aller dans le répertoire include par rapport au lieu d'installation.
Ensuite, nous informons le système de build que nous souhaitons pouvoir appeler la commande dans des projets tiers find_package(Mylib) et obtenir un objectif Mylib::mylib.
Le prochain sort doit être compris de cette façon. Lorsque dans un projet tiers nous appelons la commande find_package(Mylib 1.2.3 REQUIRED), et la version réelle de la bibliothèque installée sera incompatible avec la version 1.2.3CMake générera automatiquement une erreur. Autrement dit, vous n'aurez pas besoin de suivre les versions manuellement.
Si les tests sont explicitement désactivés en utilisant option correspondante ou notre projet est un sous-projet, c'est-à-dire qu'il est connecté à un autre projet CMake à l'aide de la commande add_subdirectory, nous n'avançons pas plus loin dans la hiérarchie, et le script, qui décrit les commandes pour générer et exécuter des tests, ne s'exécute tout simplement pas.
if(NOT MYLIB_TESTING)
message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
add_subdirectory(test)
endif()
Tout d'abord, nous trouvons un package avec le framework de test requis (remplacez par votre préféré).
find_package(doctest 2.3.3 REQUIRED)
Créons notre fichier exécutable avec des tests. Habituellement j'ajoute directement au binaire exécutable uniquement le fichier qui contiendra la fonction main.
add_executable(mylib-unit-tests test_main.cpp)
Et j'ajoute des fichiers dans lesquels les tests eux-mêmes sont décrits plus tard. Mais vous n’êtes pas obligé de faire ça.
Nous connectons les dépendances. Veuillez noter que nous avons lié uniquement les cibles CMake dont nous avions besoin à notre binaire et n'avons pas appelé la commande target_include_directories. Rubriques du framework de test et du nôtre Mylib::mylib, ainsi que les paramètres de construction (dans notre cas, il s'agit du standard du langage C++) sont venus accompagner ces objectifs.
Enfin, nous créons une cible factice, dont la « build » équivaut à l'exécution de tests, et ajoutons cette cible à la build par défaut (l'attribut est responsable de cette ALL). Cela signifie que la version par défaut déclenche l'exécution des tests, ce qui signifie que nous n'oublierons jamais de les exécuter.
add_custom_target(check ALL COMMAND mylib-unit-tests)
Ensuite, nous activons la mesure de la couverture du code si l'option appropriée est spécifiée. Je n’entrerai pas dans les détails, car ils concernent davantage un outil de mesure de couverture que CMake. Il est seulement important de noter qu'un objectif sera créé sur la base des résultats. coverage, avec lequel il est pratique de commencer à mesurer la couverture.
find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
message(STATUS "Измерение покрытия кода тестами включено")
target_compile_options(mylib-unit-tests PRIVATE --coverage)
target_link_libraries(mylib-unit-tests PRIVATE gcov)
add_custom_target(coverage
COMMAND
${GCOVR_EXECUTABLE}
--root=${PROJECT_SOURCE_DIR}/include/
--object-directory=${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
check
)
elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE)
set(MYLIB_COVERAGE OFF)
message(WARNING "Для замеров покрытия кода тестами требуется программа gcovr")
endif()
Ensuite, nous vérifions si l'utilisateur a défini la variable de langue. Si oui, alors nous n'y touchons pas, sinon nous prenons le russe. Ensuite, nous configurons les fichiers système de Doxygen. Toutes les variables nécessaires, y compris la langue, y sont placées lors du processus de configuration (voir. команду configure_file).
Ensuite, nous créons un objectif doc, qui commencera à générer de la documentation. Puisque la génération de documentation n'est pas le besoin le plus important dans le processus de développement, la cible ne sera pas activée par défaut ; elle devra être lancée explicitement.
if (Doxygen_FOUND)
if (NOT MYLIB_DOXYGEN_LANGUAGE)
set(MYLIB_DOXYGEN_LANGUAGE Russian)
endif()
message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}")
configure_file(Doxyfile.in Doxyfile)
add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()
Ici nous trouvons le troisième Python et créons une cible wandbox, qui génère une requête correspondant à l'API du service Boîte à baguettes, et le renvoie. La réponse est accompagnée d'un lien vers le bac à sable terminé.
find_program(PYTHON3_EXECUTABLE python3)
if(PYTHON3_EXECUTABLE)
set(WANDBOX_URL "https://wandbox.org/api/compile.json")
add_custom_target(wandbox
COMMAND
${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include |
curl -H "Content-type: application/json" -d @- ${WANDBOX_URL}
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS
mylib-unit-tests
)
else()
message(WARNING "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()
Offre la possibilité de désactiver la construction et la cible des tests unitaires check. En conséquence, la mesure de la couverture du code par les tests est désactivée (voir. MYLIB_COVERAGE).
Les tests sont également automatiquement désactivés si le projet est connecté à un autre projet en tant que sous-projet à l'aide de la commande add_subdirectory.
Analyses en cours d'exécution (exécute, si ce n'est déjà fait) les tests unitaires pour la couverture du code par les tests utilisant le programme gcovr.
Le service est utilisé pour cela Boîte à baguettes. Je ne sais pas à quel point leurs serveurs sont flexibles, mais je pense qu’il ne faut pas abuser de cette opportunité.
En fait, CMake version 3.13 n'est requis que pour exécuter certaines des commandes de console décrites dans cette aide. Du point de vue de la syntaxe des scripts CMake, la version 3.8 est suffisante si la génération est appelée autrement.
Après cela, l'analyse statique sera automatiquement lancée à chaque fois que la source sera compilée et recompilée. Il n'est pas nécessaire de faire quoi que ce soit de plus.
Bruit
Avec l'aide d'un merveilleux outil scan-build Vous pouvez également exécuter une analyse statique en un rien de temps :
CMake est un système très puissant et flexible qui vous permet de mettre en œuvre des fonctionnalités pour tous les goûts et toutes les couleurs. Et, même si la syntaxe laisse parfois beaucoup à désirer, le diable n'est toujours pas aussi terrible qu'on le peint. Utilisez le système de construction CMake au profit de la société et de la santé.