C++ de l'organisation du projet (avec gtest, cmake et doxygen)
Je suis nouveau en programmation en général, donc j'ai décidé de commencer par faire une simple classe vector en C++. Cependant, j'aimerais obtenir de bonnes habitudes dès le début plutôt que d'essayer de modifier mon flux de travail plus tard.
Je ne disposent actuellement que de deux fichiers vector3.hpp
et vector3.cpp
. Ce projet va lentement commencer à se développer (ce qui la rend beaucoup plus générales sur l'algèbre linéaire bibliothèque) que je deviens de plus en plus familiers avec tout, donc je voudrais adopter un "standard" de présentation de projet pour rendre la vie plus facile plus tard. Donc, après avoir regardé autour, j'ai trouvé deux façons d'organiser php et les fichiers cpp, la première étant:
project
└── src
├── vector3.hpp
└── vector3.cpp
et le second:
project
├── inc
│ └── project
│ └── vector3.hpp
└── src
└── vector3.cpp
Qui recommanderiez-vous et pourquoi?
Deuxièmement, je voudrais utiliser le Google C++ Cadre d'Essais pour les tests unitaires mon code car il semble assez facile à utiliser. Avez-vous suggèrent le groupage avec mon code, par exemple dans un inc/gtest
ou contrib/gtest
dossier? Si groupés, ne vous suggérons d'utiliser le fuse_gtest_files.py
script pour réduire le nombre de fichiers, ou de les laisser comme c'est? Si pas regroupés de la façon dont est cette dépendance traitées?
Quand il s'agit de l'écriture de tests, comment sont-ils organisés généralement? Je pensais l'avoir un fichier cpp pour chaque classe (test_vector3.cpp
par exemple), mais le tout compilé dans un binaire pour qu'ils puissent tous être exécutées facilement?
Depuis le gtest bibliothèque est généralement construire en utilisant cmake et de faire, je pensais que ce serait utile pour mon projet aussi être construit comme cela? Si j'ai décidé d'utiliser la suite de présentation de projet:
├── CMakeLists.txt
├── contrib
│ └── gtest
│ ├── gtest-all.cc
│ └── gtest.h
├── docs
│ └── Doxyfile
├── inc
│ └── project
│ └── vector3.cpp
├── src
│ └── vector3.cpp
└── test
└── test_vector3.cpp
Comment le CMakeLists.txt
avez à regarder de sorte qu'il peut soit construire la bibliothèque, la bibliothèque et les tests? J'ai aussi vu pas mal de projets qui ont un build
et un bin
répertoire. Le build se produire dans le répertoire de construction, puis les fichiers binaires déplacé dans le répertoire bin? Serait les binaires pour les tests et la bibliothèque de vivre dans le même endroit? Ou serait-il plus logique de le structurer comme suit:
test
├── bin
├── build
└── src
└── test_vector3.cpp
Je tiens également à utiliser doxygen pour documenter mon code. Est-il possible d'obtenir ce à exécuter automatiquement avec cmake et faire?
Désolé pour toutes les questions, mais je n'ai pas trouvé un livre sur le C++ qui répond de manière satisfaisante ce type de questions.
- La grande question, mais je ne pense pas que c'est un bon ajustement pour Stack Overflow's Q&Un format. Je suis très intéressé par une réponse si. +1 & fav
- Ce sont beaucoup de question sur énorme. Mai il est préférable de les diviser en plusieurs petites questions et des liens vers d'autres. De toute façon à répondre à la dernière partie: Avec CMake vous pouvez choisir de construire à l'intérieur et à l'extérieur de votre répertoire src (je vous recommande à l'extérieur). Et oui, vous pouvez utiliser doxygen avec CMake.
Vous devez vous connecter pour publier un commentaire.
C++ systèmes de construction sont un peu de la magie noire et les plus âgés le projet
la plus étrange des choses que vous pouvez trouver de sorte qu'il n'est pas surprenant que beaucoup
des questions se posent. Je vais essayer de marcher à travers les questions une par une et de mentionner quelques choses générales concernant la création de bibliothèques C++.
Séparant les en-têtes et les fichiers cpp dans les annuaires. Ce n'est qu'
indispensable si vous créez un composant qui est censé être utilisé
comme une bibliothèque, par opposition à une application réelle. Vos en-têtes sont les
base pour les utilisateurs d'interagir avec ce que vous offrez et doit être
installé. Cela signifie qu'ils doivent être dans un sous-répertoire (personne ne veut
beaucoup d'en-têtes de finir dans le top-niveau
/usr/include/
) et votreles en-têtes doivent être en mesure d'inclure eux-mêmes avec une telle configuration.
fonctionne bien, parce que comprennent les chemins de travailler et vous pouvez utiliser facile
d'expansion pour installer des cibles.
Regroupement des dépendances: je pense que cela dépend en grande partie sur la capacité de
le système de construction pour localiser et configurer les dépendances et comment
dépendant de votre code sur une seule version. Elle dépend aussi de la façon dont
mesure de vos utilisateurs et comment facile est la dépendance à installer sur leur
la plate-forme. CMake est livré avec un
find_package
script pour GoogleTest. Cela rend les choses beaucoup plus facile. Je voudrais aller avec le regroupement seulement
en cas de besoin et d'éviter autrement.
Comment construire: Éviter à la source s'appuie. CMake fait de la source s'appuie
facile et ça rend la vie beaucoup plus facile.
Je suppose que vous aussi, vous souhaitez utiliser CTest pour exécuter des tests pour votre système (c'
est également livré avec construire-dans le soutien à GTest). Une décision importante pour
annuaire de mise en page et de l'organisation de tests seront: - vous jusqu'à la fin avec
sous-projets? Si oui, vous avez besoin d'un peu plus de travail lors de la configuration de CMakeLists
et devrait diviser votre sous-projets dans les sous-répertoires, chacun avec ses
propre
include
etsrc
fichiers. Peut-être même leur propre doxygen s'exécute etsorties (combinaison de plusieurs doxygen projets est possible, mais pas facile
ou assez).
Vous allez vous retrouver avec quelque chose comme ceci:
où
Dans le cas où vous avez des sous-composants, je voudrais suggérer l'ajout d'une autre hiérarchie et de l'utilisation de l'arbre ci-dessus pour chaque sous-projet. Puis les choses se complique, parce que vous devez décider si les sous-composantes de la recherche et de configurer leurs dépendances ou si vous le faites dans le top-niveau. Cela doit être décidé au cas par cas.
Doxygen: Après avoir réussi à passer à travers la configuration de la danse de
doxygen, c'est facile à utiliser CMake
add_custom_command
pour ajouter undoc cible.
Mes projets de fin et j'ai vu de très des projets similaires, mais bien sûr ce n'est pas la panacée à tous.
Additif À un certain point, vous aurez envie de générer un
config.hpp
fichier qui contient une version de définir et peut-être de définir à une certaine version
contrôle de l'identificateur (un Git de hachage ou de numéro de révision SVN). CMake a
modules pour automatiser la recherche que de l'information et de générer des
les fichiers. Vous pouvez utiliser CMake est
configure_file
de remplacer les variables dans unfichier de modèle avec les variables définies à l'intérieur de la
CMakeLists.txt
.Si vous êtes à la construction des bibliothèques, vous aurez également besoin d'une exportation à définir
obtenez la différence entre les compilateurs droit, par exemple,
__declspec
sur MSVCet
visibility
attributs sur GCC/clang.Comme un démarreur, il y a quelques classiques des noms de répertoires que vous ne pouvez pas ignorer, elles sont basées sur la longue tradition avec le système de fichiers Unix. Ce sont:
C'est probablement une bonne idée de s'en tenir à cette structure de base, au moins au niveau supérieur.
Sur le fractionnement des fichiers d'en-tête et les fichiers source (rpc), les deux systèmes sont assez communs. Cependant, j'ai tendance à préférer les garder ensemble, il est juste plus pratique au jour le jour les tâches de l'ensemble des fichiers. Aussi, quand tout le code est en vertu d'un dossier de niveau supérieur, c'est à dire, la
trunk/src/
dossier, vous pouvez remarquer que tous les autres dossiers (bin, lib, include, doc, et peut-être quelques test de dossier) au plus haut niveau, en plus de la "construction" de répertoire pour une source de construire, sont tous les dossiers qui contiennent rien de plus que les fichiers sont générés dans le processus de construction. Et donc, seul le dossier src doit être sauvegardé, ou beaucoup mieux, conservés en vertu d'un système de contrôle de version /serveur (comme Git ou SVN).Et quand il s'agit de l'installation de votre en-tête des fichiers sur le système de destination (si vous voulez éventuellement distribuer votre bibliothèque), eh bien, CMake a une commande pour l'installation des fichiers (crée implicitement une "installation" de la cible, de faire un "make install") que vous pouvez utiliser pour mettre tous les en-têtes dans le
/usr/include/
répertoire. Je viens d'utiliser la suite de cmake macro à cet effet:Où
SRCROOT
est un cmake variable que j'ai mis à le dossier src etINCLUDEROOT
est cmake variable que je configurer partout sur les en-têtes doivent aller. Bien sûr, il ya beaucoup d'autres façons de le faire, et je suis sûr que mon chemin n'est pas le meilleur. Le point est, il n'y a aucune raison pour diviser les en-têtes et les sources juste parce que les en-têtes doivent être installés sur le système cible, car il est très facile, surtout avec CMake (ou CPack), de choisir et de configurer les en-têtes pour être installé sans avoir à disposer d'eux dans un répertoire séparé. Et c'est ce que j'ai vu dans la plupart des bibliothèques.Ne pas bundle dépendances avec votre bibliothèque. Ce est généralement une jolie idée horrible, et j'ai toujours déteste quand je suis coincé à essayer de construire une bibliothèque qui l'a fait. Il devrait être votre dernier recours, et méfiez-vous des pièges. Souvent, les gens bundle dépendances avec leur bibliothèque, soit parce qu'ils la cible d'un terrible environnement de développement (par exemple, Windows), ou parce qu'ils prennent uniquement en charge un vieux (obsolète) de la version de la bibliothèque (de la dépendance) en question. Le principal écueil est que votre forfait dépendance qui pourraient entrer en conflit avec déjà les versions installées de la même bibliothèque /application (par exemple, vous avez assemblé gtest, mais la personne qui tente de construire votre bibliothèque a déjà un nouveau (ou plus) de la version de gtest déjà installé, puis les deux peuvent entrer en conflit et de lui donner un très méchant mal de tête). Donc, comme je l'ai dit, le faire à vos propres risques, et je dirais que comme un dernier recours. Demandant aux gens d'installer quelques dépendances avant d'être en mesure de compiler votre bibliothèque est un moindre mal que d'essayer de résoudre les conflits entre dans votre forfait dépendances et installations existantes.
Un fichier cpp par classe (ou un petit groupe homogène de classes et de fonctions) est plus que d'habitude et de pratique à mon avis. Toutefois, sans doute, de ne pas compiler le tout dans un binaire juste pour que "ils peuvent fonctionner ensemble". C'est vraiment une mauvaise idée. Généralement, quand il s'agit de codage, vous voulez diviser les choses autant qu'il est raisonnable de le faire. Dans le cas de l'unité-tests, vous ne voulez pas un binaire à exécuter tous les tests, parce que cela signifie que la moindre petite modification que vous apportez à quelque chose dans votre bibliothèque est susceptible de causer un quasi-totale de la recompilation de l'unité de programme de test, et c'est à seulement quelques minutes /heures perdues en attente pour la recompilation. Il suffit de coller à un schéma simple: 1 unité = 1 unité-programme de test. Ensuite, utiliser un script ou un test unitaire cadre (comme gtest et/ou CTest) pour exécuter tous les programmes de test et le rapport à l'échec/taux de réussite.
Je préfère suggérer cette mise en page:
Quelques choses à noter ici. Tout d'abord, les sous-répertoires de votre répertoire src devrait être le reflet de la sous-répertoires de votre répertoire, c'est juste pour garder les choses intuitive (aussi, essayez de garder votre sous-structure de répertoire raisonnablement plat (peu), parce que la profonde imbrication des dossiers est souvent plus une corvée qu'autre chose). Deuxièmement, la "inclure" répertoire est juste un répertoire d'installation, son contenu est juste ce que les en-têtes sont choisi du répertoire src.
Troisième, le CMake système est destiné à être distribué sur la source des sous-répertoires, non pas comme un CMakeLists.txt fichier au haut-niveau. Cela permet de maintenir les choses locale, et c'est bon (dans l'esprit de diviser les choses en indépendants des pièces). Si vous ajoutez une nouvelle source, un nouvel en-tête, ou d'un nouveau programme de test, vous avez besoin d'éditer un petit et simple CMakeLists.txt fichier dans le sous-répertoire en question, sans toucher à rien d'autre. Cela vous permet également de restructurer les répertoires avec facilité (CMakeLists sont locales et contenue dans le sous-répertoires être déplacé). Le haut-niveau CMakeLists doit contenir la plupart des configurations de premier niveau, tels que la configuration des répertoires de destination, des commandes personnalisées (ou macros), et de trouver des paquets installés sur le système. Le niveau inférieur CMakeLists ne doit contenir que de simples listes de têtes, des sources et des tests unitaires sources, et le cmake commandes que vous inscrire à la compilation des cibles.
De base de la réponse est que CMake permet d'exclure expressément certains objectifs de "tous" (qui est ce qui est construit lorsque vous tapez "make"), et vous pouvez également créer des faisceaux de cibles. Je ne peux pas faire un CMake tutoriel ici, mais il est assez simple de trouver par vous-même. Dans ce cas précis, cependant, la solution recommandée est, bien sûr, à utiliser CTest, qui est juste un jeu supplémentaire de commandes que vous pouvez utiliser dans la CMakeLists fichiers à enregistrer un certain nombre d'objectifs (programmes) qui sont marqués comme unité-tests. Donc, CMake va mettre tous les tests dans une catégorie spéciale de construit, et c'est exactement ce que vous avez demandé, donc, le problème est résolu.
Avoir un répertoire de construction à l'extérieur de la source ("out-of-source" build) c'est vraiment la seule sane chose à faire, c'est le standard de facto de ces jours. Donc, certainement, avoir un "build" de répertoire, en dehors du répertoire des sources, tout comme la CMake personnes recommandent, et comme chaque programmeur, je n'ai jamais rencontré n'. Comme pour le répertoire bin, et bien, c'est une convention, et c'est probablement une bonne idée de s'y tenir, comme je l'ai dit au début de ce post.
Oui. Il est plus que possible, il est génial. En fonction de la façon de fantaisie, vous voulez obtenir, il y a plusieurs possibilités. CMake n'ont un module pour Doxygen (c'est à dire,
find_package(Doxygen)
) qui vous permet de vous inscrire cibles qui seront exécutés Doxygen sur certains fichiers. Si vous voulez faire plus de choses de fantaisie, comme la mise à jour du numéro de version dans le Doxyfile, ou automatiquement à la saisie d'une date /auteur timbres pour les fichiers source et ainsi de suite, il est possible avec un peu de CMake kung-fu. Généralement, cela impliquera que vous gardez une source Doxyfile (par exemple, le "Doxyfile.dans" que j'ai mis dans le dossier de présentation ci-dessus) qui a des jetons pour être trouvé et remplacé par CMake de l'analyse des commandes. Dans mon top niveau CMakeLists fichier, vous trouverez un de ces morceau de CMake kung-fu qui n'est quelques choses de fantaisie avec cmake-doxygen ensemble.main.cpp
devrait aller àtrunk/bin
?Structurer le projet
Je voudrais favorisent généralement les suivantes:
Cela signifie que vous avez une très ensemble clairement défini de l'API de fichiers de votre bibliothèque, et la structure signifie que les clients de votre bibliothèque ne
plutôt que le moins explicite
J'aime la structure de l' /src arbre correspondre à celui de la /des arbres, mais c'est une préférence personnelle pour vraiment. Toutefois, si votre projet se développe à contenir des sous-répertoires dans /include/projet, il serait généralement aider à correspondre à ceux de l'intérieur, de l' /src arbre.
Pour les tests, je suis en faveur de leur maintien sur "fermer" pour les fichiers de test, et si vous vous retrouvez avec des sous-répertoires dans /src, il est assez facile de paradigme pour les autres à suivre s'ils veulent trouver un fichier de code de test.
Tests
Gtest est en effet simple d'utilisation et assez complet en fonction de ses capacités. Il peut être utilisé avec la gmock très facilement d'étendre ses capacités, mais de mes propres expériences avec gmock ont été moins favorables. Je suis tout à fait prêt à accepter qu'il peut être par mes propres défauts, mais gmock tests tendance à être plus difficile à créer, et beaucoup plus fragiles et difficiles à maintenir. Un gros clou dans le gmock cercueil, c'est qu'il n'a vraiment pas jouer gentil avec les pointeurs intelligents.
C'est un très trivial et subjective de la réponse à une question énorme (ce qui, probablement, n'appartient pas vraiment sur S. O.)
Je préfère utiliser CMake est
ExternalProject_Add
module. Ceci vous évite d'avoir à garder gtest code source dans votre référentiel, ou de l'installer n'importe où. Il est téléchargé et construit dans votre arbre de construction automatiquement.Voir mon réponse de traiter avec les détails ici.
Bon plan.
Bâtiment
Je suis un fan de CMake, mais comme avec votre test-questions connexes, S. O. est probablement pas le meilleur endroit pour demander des avis sur une question subjective.
La bibliothèque apparaît comme une cible "ProjectLibrary", et la suite de test comme une cible "ProjectTest". En spécifiant la bibliothèque comme une dépendance de l'essai exe, la construction du test exe automatiquement entraîner la bibliothèque pour être reconstruit si il est à jour.
CMake recommande "out-of-source" s'appuie, soit vous créez votre propre répertoire de construction à l'extérieur du projet et de lancer CMake à partir de là. Cela évite de "polluer" votre arborescence des sources avec les fichiers de compilation, et il est très souhaitable si vous utilisez un vcs.
Vous peut préciser que les binaires sont déplacés ou copiés dans un répertoire différent, une fois construit, ou qu'ils sont créés par défaut dans un autre répertoire, mais il n'y a généralement pas besoin. CMake propose des façons d'installer votre projet si vous le souhaitez, ou de le rendre facile pour d'autres CMake projets de "trouver" les fichiers de votre projet.
En ce qui concerne CMake propre soutien pour la recherche et l'exécution de tests gtest, cette mesure serait très approprié si vous construisez gtest dans le cadre de votre projet. Le
FindGtest
module est vraiment conçu pour être utilisé dans le cas où gtest a été construit séparément en dehors de votre projet.CMake dispose de son propre framework de test (CTest), et, idéalement, tous les gtest cas serait ajouté en tant que CTest cas.
Cependant, la
GTEST_ADD_TESTS
macro fournie parFindGtest
afin de faciliter l'ajout de gtest cas individuels, ctest cas est un peu en manque en ce qu'il ne fonctionne pas pour gtest macros autres queTEST
etTEST_F
. La valeur ou Type paramétré tests à l'aide deTEST_P
,TYPED_TEST_P
, etc. ne sont pas traitées du tout.Le problème n'a pas de solution facile à ce que je sache. Le plus robuste de manière à obtenir une liste de gtest cas, c'est pour exécuter le test exe avec le drapeau
--gtest_list_tests
. Cependant, ceci ne peut être fait qu'une fois le fichier exe est construit, donc CMake ne pouvez pas faire usage de cette. Qui vous laisse avec deux choix; CMake doit essayer d'analyser le code C++ pour en déduire le nom des tests (non trivial à l'extrême si vous voulez prendre en compte tous les gtest macros, commenté de tests, les handicapés tests), ou de cas de test sont ajoutées à la main à l'CMakeLists.txt fichier.Oui - bien que je n'ai aucune expérience sur ce front. CMake fournit
FindDoxygen
à cette fin.En plus de l'autre (excellent) réponses, je vais décrire une structure que j'ai été en utilisant relativement à grande échelle projets.
Je ne vais pas à l'adresse de subquestion sur Doxygen, car je voudrais simplement répéter ce qui est dit dans les autres réponses.
Justification
Pour la modularité et la facilité de gestion, le projet est organisé comme un ensemble de petites unités.
Pour plus de clarté, nous allons les nommer UnitX, avec X = A, B, C, ... (mais ils peuvent avoir n'importe quel nom général).
La structure de répertoire est ensuite organisée afin de refléter ce choix, avec la possibilité pour les unités du groupe si nécessaire.
Solution
Le répertoire de base de mise en page est le suivant (le contenu des unités est détaillé plus tard):
project/CMakeLists.txt
pourrait contenir les éléments suivants:et
project/GroupA/CMakeLists.txt
:et
project/GroupB/CMakeLists.txt
:Maintenant à la structure des unités différentes (prenons, comme exemple, l'UnitD)
De différentes composantes:
.cpp
) et les en-têtes (.h
) dans le même dossier. Cela permet d'éviter une double hiérarchie de répertoire, rend la maintenance plus facile. Pour l'installation, il n'est pas un problème (surtout avec CMake) de filtrer les fichiers d'en-tête.UnitD
est pour plus tard permettre y compris les fichiers avec#include <UnitD/ClassA.h>
. Aussi, lors de l'installation de cette unité, vous pouvez simplement copier la structure de répertoire est. Notez que vous pouvez également organiser vos fichiers dans des sous-répertoires.README
fichier pour résumer ce que l'unité est sur et spécifier les informations utiles à ce sujet.CMakeLists.txt
peut simplement contenir:lib/CMakeLists.txt
:Ici, notez qu'il n'est pas nécessaire de dire à CMake que nous voulons inclure des répertoires pour
UnitA
etUnitC
, comme cela a déjà été spécifié lors de la configuration de ces unités. Aussi,PUBLIC
dira toutes les cibles qui dépendent deUnitD
qu'ils devraient inclure automatiquement lesUnitA
de dépendance, alors qu'UnitC
ne sera pas nécessaire, alors (PRIVATE
).test/CMakeLists.txt
(voir ci-dessous si vous souhaitez utiliser GTest pour elle):À L'Aide De GoogleTest
Pour Google Test, le plus simple est si sa source est présent quelque part dans votre répertoire source, mais vous n'avez pas à ajouter là-même.
J'ai été en utilisant ce projet pour télécharger automatiquement, et je l'envelopper de son utilisation dans une fonction, assurez-vous qu'il est téléchargé qu'une seule fois, même si nous avons de test de plusieurs cibles.
Ce CMake fonction est la suivante:
et puis, quand je veux l'utiliser à l'intérieur de l'une de mes cibles de test, je vais ajouter les lignes suivantes à la
CMakeLists.txt
(c'est pour l'exemple ci-dessus,test/CMakeLists.txt
):