
Pendant le processus de développement, j'aime changer les compilateurs, les modes de construction, les versions des dépendances, effectuer une analyse statique, mesurer les performances, collecter la couverture, générer de la documentation, etc. Et j'aime vraiment CMake, car cela me permet de faire tout ce que je veux.
Beaucoup grondent CMake, et souvent à juste titre, mais si vous regardez, tout n'est pas si mauvais, et derniÚrement c'est trÚs bon , et la direction du développement est assez positive.
Dans cet article, je veux dire Ă quel point il est simple d'organiser une bibliothĂšque d'en-tĂȘte C ++ dans le systĂšme CMake pour obtenir les fonctionnalitĂ©s suivantes:
- Assemblée;
- Tests de démarrage automatique;
- Mesure de la couverture du code;
- Installation;
- Documentation automatique
- Génération de sandbox en ligne;
- Analyse statique
Quiconque comprend déjà les avantages et la s-make peut simplement télécharger le modÚle de projet et commencer à l'utiliser.
Table des matiĂšres
- Projet de l'intérieur
- Structure du projet
- Fichier CMake principal (./CMakeLists.txt)
- Informations sur le projet
- Options de projet
- Options de compilation
- Objectif principal
- L'installation
- Les tests
- La documentation
- Sandbox en ligne
- Script pour les tests (test / CMakeLists.txt)
- Test
- Couverture
- Script pour la documentation (doc / CMakeLists.txt)
- Script pour un sandbox en ligne (online / CMakeLists.txt)
- Projet extérieur
- Assemblage
- Génération
- Assemblage
- Les options
- MYLIB_COVERAGE
- MYLIB_TESTING
- MYLIB_DOXYGEN_LANGUAGE
- Objectifs de l'Assemblée
- Par défaut
- tests unitaires mylib
- vérifier
- couverture
- doc
- wandbox
- Des exemples
- Les outils
- Analyse statique
- Postface
. âââ CMakeLists.txt âââ README.en.md âââ README.md âââ doc â âââ CMakeLists.txt â âââ Doxyfile.in âââ include â âââ mylib â âââ myfeature.hpp âââ online â âââ CMakeLists.txt â âââ mylib-example.cpp â âââ wandbox.py âââ test âââ CMakeLists.txt âââ mylib â âââ myfeature.cpp âââ test_main.cpp
Nous parlerons principalement de la façon d'organiser les scripts CMake, ils seront donc analysés en détail. Tout le monde peut voir le reste des fichiers directement sur la page du modÚle de projet .
Tout d'abord, vous devez demander la bonne version du systÚme CMake. CMake évolue, les signatures d'équipe, les comportements dans différentes conditions changent. Pour que CMake comprenne immédiatement ce que nous attendons de lui, nous devons immédiatement fixer nos exigences pour lui.
cmake_minimum_required(VERSION 3.13)
Nous désignons ensuite notre projet, son nom, sa version, les langues utilisées, etc. (voir project
).
Dans ce cas, nous CXX
langage CXX
(ce qui signifie C ++) afin que CMake ne soit pas contraint et ne recherche pas le compilateur de langage C (par défaut, deux langages sont inclus dans CMake: C et C ++).
project(Mylib VERSION 1.0 LANGUAGES CXX)
Ici, vous pouvez immédiatement vérifier si notre projet est inclus dans un autre projet en tant que sous-projet. Cela aidera grandement à l'avenir.
get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
Nous proposons deux options.
La premiĂšre option - 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 et que nous voulons, par exemple, uniquement installer ou conditionner notre projet. Ou notre projet est inclus en tant que sous-projet - dans ce cas, l'utilisateur de notre projet n'est pas intĂ©ressĂ© par l'exĂ©cution de nos tests. Vous ne testez pas les dĂ©pendances que vous utilisez?
option(MYLIB_TESTING " " ON)
De plus, nous MYLIB_COVERAGE
une option distincte MYLIB_COVERAGE
pour mesurer la couverture du code avec des tests, mais cela nécessitera des outils supplémentaires, vous devrez donc l'activer explicitement.
option(MYLIB_COVERAGE " " OFF)
Bien sûr, nous sommes des programmeurs plus sympas, nous voulons donc le niveau maximum de diagnostics de temps de compilation du compilateur. Pas une seule souris ne passera.
add_compile_options( -Werror -Wall -Wextra -Wpedantic -Wcast-align -Wcast-qual -Wconversion -Wctor-dtor-privacy -Wenum-compare -Wfloat-equal -Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wsign-conversion -Wsign-promo )
Nous désactiverons également les extensions pour se conformer pleinement à la norme de langage C ++. Par défaut, ils sont inclus dans CMake.
if(NOT CMAKE_CXX_EXTENSIONS) set(CMAKE_CXX_EXTENSIONS OFF) endif()
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. D'un autre cĂŽtĂ©, pour utiliser notre bibliothĂšque Ă l'extĂ©rieur, vous devez l'installer, vous devez pouvoir la trouver dans le systĂšme et la connecter Ă votre projet, et en mĂȘme temps ces mĂȘmes en-tĂȘtes sont liĂ©s avec elle, ainsi que, peut-ĂȘtre, quelques autres propriĂ©tĂ©s.
Pour cela, nous créons une bibliothÚque d'interfaces.
add_library(mylib INTERFACE)
Liez les en-tĂȘtes Ă notre bibliothĂšque frontale.
L'utilisation moderne, Ă la mode et jeune de CMake implique que les en-tĂȘtes, les propriĂ©tĂ©s, etc. transmis dans un seul but. Ainsi, il suffit de dire target_link_libraries(target PRIVATE dependency)
, et tous les en-tĂȘtes associĂ©s Ă la cible de dependency
seront disponibles pour les sources appartenant Ă la cible target
. Et aucun [target_]include_directories
n'est requis. Cela sera démontré ci-dessous lors de l'analyse du script CMake pour les tests unitaires .
Il convient Ă©galement de prĂȘter attention aux -: $<...>
.
Cette commande associe les en-tĂȘtes dont nous avons besoin Ă notre bibliothĂšque frontale, et si notre bibliothĂšque est connectĂ©e Ă une cible au sein de la mĂȘme hiĂ©rarchie CMake, alors les en-tĂȘtes du rĂ©pertoire ${CMAKE_CURRENT_SOURCE_DIR}/include
y seront associĂ©s, et si la nĂŽtre Ătant donnĂ© que la bibliothĂšque est installĂ©e sur le systĂšme et connectĂ©e Ă un autre projet Ă l'aide de la find_package
, les en-tĂȘtes du rĂ©pertoire include
relatifs au répertoire d'installation lui seront associés.
target_include_directories(mylib INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
DĂ©finissez la norme de langue. Bien sĂ»r, le tout dernier. Dans le mĂȘme temps, nous incluons non seulement la norme, mais la distribuons Ă©galement Ă ceux qui utiliseront notre bibliothĂšque. Cela est dĂ» au fait que la propriĂ©tĂ© set a la catĂ©gorie INTERFACE
(voir la commande target_compile_features ).
target_compile_features(mylib INTERFACE cxx_std_17)
Nous créons un alias pour notre bibliothÚque. De plus, pour la beauté, il sera dans un "espace de noms" spécial. Cela sera utile lorsque différents modules apparaissent dans notre bibliothÚque, et nous allons les connecter indépendamment les uns des autres. Comme dans Boost, par exemple .
add_library(Mylib::mylib ALIAS mylib)
Installer nos en-tĂȘtes dans le systĂšme. Ici, tout est simple. Nous disons que le dossier avec tous les en-tĂȘtes doit ĂȘtre dans le rĂ©pertoire include
relatif Ă l'emplacement d'installation.
install(DIRECTORY include/mylib DESTINATION include)
Ensuite, nous informons le systĂšme de construction que nous voulons pouvoir appeler find_package(Mylib)
dans des projets tiers et obtenir la cible Mylib::mylib
.
install(TARGETS mylib EXPORT MylibConfig) install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)
Le sort suivant doit ĂȘtre compris comme suit. Lorsque nous appelons find_package(Mylib 1.2.3 REQUIRED)
dans un projet tiers, et dans ce cas, la version réelle de la bibliothÚque installée sera incompatible avec la version 1.2.3
, CMake générera automatiquement une erreur. Autrement dit, vous n'aurez pas besoin de suivre les versions manuellement.
include(CMakePackageConfigHelpers) write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake)
Si les tests sont désactivés explicitement en utilisant l' option appropriée ou si notre projet est un sous-projet, c'est-à -dire connecté à un autre projet CMake à l'aide de la add_subdirectory
, nous n'irons pas plus loin dans la hiérarchie et le script qui décrit les commandes pour générer et exécuter les tests ne démarre tout simplement pas .
if(NOT MYLIB_TESTING) message(STATUS " Mylib ") elseif(IS_SUBPROJECT) message(STATUS "Mylib ") else() add_subdirectory(test) endif()
La documentation ne sera pas non plus générée dans le cas d'un sous-projet.
if(NOT IS_SUBPROJECT) add_subdirectory(doc) endif()
De mĂȘme, le sous-projet n'aura pas non plus de bacs Ă sable en ligne.
if(NOT IS_SUBPROJECT) add_subdirectory(online) endif()
Tout d'abord, nous trouvons le package avec le framework de test souhaité (remplacez-le par votre favori).
find_package(doctest 2.3.3 REQUIRED)
Nous créons notre fichier exécutable avec des tests. Habituellement, j'ajoute uniquement le fichier dans lequel la fonction main
sera directement au binaire exécutable.
add_executable(mylib-unit-tests test_main.cpp)
Et les fichiers qui dĂ©crivent les tests eux-mĂȘmes sont ajoutĂ©s ultĂ©rieurement. Mais ce n'est pas nĂ©cessaire.
target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)
Nous connectons les dépendances. Veuillez noter que nous avons uniquement target_include_directories
cibles CMake dont nous avions besoin à notre binaire et n'avons pas appelé la commande target_include_directories
. Les en-tĂȘtes du framework de test et de notre Mylib::mylib
, ainsi que les paramÚtres de build (dans notre cas, c'est le standard du langage C ++) ont exploré ces objectifs.
target_link_libraries(mylib-unit-tests PRIVATE Mylib::mylib doctest::doctest )
Enfin, créez une cible fictive, dont le «assembly» équivaut à l'exécution de tests, et ajoutez cette cible à l'assembly par défaut (l'attribut ALL
est responsable). Cela signifie que l'assembly par défaut initie le lancement des tests, c'est-à -dire 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 correspondante est spécifiée. Je n'entrerai pas dans les détails, car ils concernent plus l'outil de mesure de la couverture que CMake. Il est seulement important de noter que, sur la base des résultats, un objectif de coverage
sera créé, avec lequel il est commode 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()
Trouvé Doxygen .
find_package(Doxygen)
Ensuite, nous vérifions si l'utilisateur a défini la variable de langue. Si oui, ne touchez pas, sinon, prenez le russe. Configurez ensuite les fichiers systÚme Doxygen. Toutes les variables nécessaires, y compris la langue, y parviennent lors du processus de configuration (voir configure_file
).
Ensuite, nous créons la cible doc
, qui commencera la gĂ©nĂ©ration de la documentation. Ătant donnĂ© que la gĂ©nĂ©ration de documentation n'est pas le plus grand besoin dans le processus de dĂ©veloppement, par dĂ©faut, l'objectif ne sera pas activĂ©, il devra ĂȘtre dĂ©marrĂ© 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 demande qui correspond à l'API du service Wandbox et l'envoie. En réponse, un lien vers le bac à sable fini vient.
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()
Considérez maintenant comment tout utiliser.
L'assemblage de ce projet, comme tout autre projet sur le systÚme d'assemblage CMake, se compose de deux étapes:
cmake -S // -B /// [ ...]
Si la commande ci-dessus n'a pas fonctionné en raison de l'ancienne version de CMake, essayez d'omettre -S
:
cmake // -B /// [ ...]
En savoir plus sur les options .
cmake --build /// [--target target]
En savoir plus sur les objectifs d'assemblage .
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [ ...]
Comprend un objectif de coverage
, avec lequel vous pouvez commencer Ă mesurer la couverture de code avec des tests.
cmake -S ... -B ... -DMYLIB_TESTING=OFF [ ...]
Permet de désactiver l'assemblage de test unitaire et la cible de check
. Par conséquent, la mesure de la couverture du code par les tests est désactivée (voir MYLIB_COVERAGE
).
De plus, le test est automatiquement désactivé si le projet est connecté à un autre projet en tant que sous-projet à l'aide de la add_subdirectory
.
cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [ ...]
Bascule le langage de doc
généré par la cible doc
vers celui spécifié. Pour une liste des langues disponibles, consultez le site Web de Doxygen .
Par défaut, le russe est activé.
cmake --build path/to/build/directory cmake --build path/to/build/directory --target all
Si la cible n'est pas spécifiée (ce qui équivaut à l'objectif all
), collecte tout ce qui est possible et appelle également la cible de check
.
cmake --build path/to/build/directory --target mylib-unit-tests
Compile les tests unitaires. Activé par défaut.
cmake --build /// --target check
Exécute les tests unitaires collectés (collecte, si pas encore). Activé par défaut.
Voir aussi mylib-unit-tests
.
cmake --build /// --target coverage
Analyse les tests unitaires en cours d'exécution (s'exécute, si ce n'est pas encore) pour couvrir le code avec des tests utilisant le programme gcovr .
Le revĂȘtement d'Ă©chappement ressemblera Ă ceci:
------------------------------------------------------------------------------ GCC Code Coverage Report Directory: /path/to/cmakecpptemplate/include/ ------------------------------------------------------------------------------ File Lines Exec Cover Missing ------------------------------------------------------------------------------ mylib/myfeature.hpp 2 2 100% ------------------------------------------------------------------------------ TOTAL 2 2 100% ------------------------------------------------------------------------------
La cible n'est disponible que lorsque MYLIB_COVERAGE
.
Voir aussi check
.
cmake --build /// --target doc
Démarre la génération de la documentation du code à l'aide du systÚme Doxygen .
cmake --build /// --target wandbox
La réponse du service ressemble à ceci:
{ "permlink" : "QElvxuMzHgL9fqci", "status" : "0", "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci" }
Pour ce faire, utilisez le service Wandbox . Je ne sais pas comment ils ont des serveurs en caoutchouc, mais je pense que cette opportunitĂ© ne doit pas ĂȘtre abusĂ©e.
Assemblage de projet en mode débogage avec mesure de couverture
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON cmake --build /// --target coverage --parallel 16
Installation du projet sans assemblage ni test préliminaires
cmake -S // -B /// -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/// cmake --build /// --target install
Construire en mode release par le compilateur spécifié
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=///// cmake --build /// --parallel 4
Génération de documentation en anglais
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English cmake --build /// --target doc
CMake 3.13
En fait, CMake 3.13 n'est requis que pour exécuter certaines des commandes de la console décrites dans cette aide. Du point de vue syntaxique des scripts CMake, la version 3.8 est suffisante si la génération est appelée d'une autre maniÚre.
BibliothĂšque de tests Doctest
Les tests peuvent ĂȘtre dĂ©sactivĂ©s (voir l' MYLIB_TESTING
).
Doxygen
Pour changer la langue dans laquelle la documentation sera générée, l'option MYLIB_DOXYGEN_LANGUAGE
est MYLIB_DOXYGEN_LANGUAGE
.
InterprĂšte Python 3
Pour générer automatiquement des sandbox en ligne .
Avec CMake et quelques bons outils, vous pouvez fournir une analyse statique avec un minimum de mouvements corporels.
Cppcheck
CMake a un support intégré pour l' outil d'analyse statique Cppcheck .
Pour ce faire, utilisez l'option CMAKE_CXX_CPPCHECK
:
cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I///include"
AprÚs cela, l'analyse statique sera automatiquement lancée à chaque fois pendant la compilation et la recompilation des sources. Vous n'avez rien à faire de plus.
Clang
En utilisant le merveilleux outil de scan-build
, vous pouvez également exécuter une analyse statique en deux temps:
scan-build cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug scan-build cmake --build ///
Ici, contrairement au cas avec Cppcheck, vous devez exécuter l'assembly à chaque fois via scan-build
.
CMake est un systÚme trÚs puissant et flexible qui vous permet d'implémenter des fonctionnalités pour tous les goûts et toutes les couleurs. Et, bien que la syntaxe laisse parfois à désirer, le diable n'est toujours pas aussi terrible qu'il est peint. Utilisez le systÚme de construction CMake au profit de la société et de la santé.
â TĂ©lĂ©charger le modĂšle de projet