Pour commencer, il existe des noms conventionnels de répertoires que vous ne pouvez pas ignorer, ils sont basés sur la longue tradition du système de fichiers Unix. Ceux-ci sont:
trunk
├── bin : for all executables (applications)
├── lib : for all other binaries (static and shared libraries (.so or .dll))
├── include : for all header files
├── src : for source files
└── doc : for documentation
C'est probablement une bonne idée de s'en tenir à cette mise en page de base, au moins au niveau supérieur.
À propos du fractionnement des fichiers d'en-tête et des fichiers source (cpp), les deux schémas sont assez courants. Cependant, j'ai tendance à préférer les garder ensemble, il est simplement plus pratique dans les tâches quotidiennes de rassembler les fichiers. De plus, lorsque tout le code est dans un dossier de niveau supérieur, c'est-à-dire le trunk/src/
dossier, vous pouvez remarquer que tous les autres dossiers (bin, lib, include, doc, et peut-être un dossier de test) au niveau supérieur, en plus de le répertoire "build" pour une build out-of-source, sont tous les dossiers qui ne contiennent rien de plus que des fichiers qui sont générés dans le processus de build. Et ainsi, seul le dossier src doit être sauvegardé, ou mieux, conservé sous un système / serveur de contrôle de version (comme Git ou SVN).
Et quand il s'agit d'installer vos fichiers d'en-tête sur le système de destination (si vous voulez éventuellement distribuer votre bibliothèque), eh bien, CMake a une commande pour installer les fichiers (crée implicitement une cible "install", pour faire "make install") qui vous pouvez utiliser pour mettre tous les en-têtes dans le /usr/include/
répertoire. J'utilise juste la macro cmake suivante à cet effet:
# custom macro to register some headers as target for installation:
# setup_headers("/path/to/header/something.h" "/relative/install/path")
macro(setup_headers HEADER_FILES HEADER_PATH)
foreach(CURRENT_HEADER_FILE ${HEADER_FILES})
install(FILES "${SRCROOT}${CURRENT_HEADER_FILE}" DESTINATION "${INCLUDEROOT}${HEADER_PATH}")
endforeach(CURRENT_HEADER_FILE)
endmacro(setup_headers)
Où se SRCROOT
trouve une variable cmake que j'ai définie dans le dossier src, et INCLUDEROOT
est la variable cmake que je configure à l'endroit où les en-têtes doivent aller. Bien sûr, il existe de nombreuses autres façons de le faire, et je suis sûr que ma méthode n'est pas la meilleure. Le fait est qu'il n'y a aucune raison de diviser les en-têtes et les sources simplement parce que seuls les en-têtes doivent être installés sur le système cible, car il est très facile, en particulier avec CMake (ou CPack), de choisir et de configurer les en-têtes pour être installés sans avoir à les avoir dans un répertoire séparé. Et c'est ce que j'ai vu dans la plupart des bibliothèques.
Quote: Deuxièmement, je voudrais utiliser le cadre de test Google C ++ pour tester mon code unitaire car il semble assez facile à utiliser. Proposez-vous de regrouper ceci avec mon code, par exemple dans un dossier "inc / gtest" ou "contrib / gtest"? S'il est fourni, suggérez-vous d'utiliser le script fuse_gtest_files.py pour réduire le nombre de fichiers ou de le laisser tel quel? Si ce n'est pas groupé, comment cette dépendance est-elle gérée?
Ne regroupez pas les dépendances avec votre bibliothèque. C'est généralement une idée assez horrible, et je déteste toujours ça quand je suis coincé à essayer de construire une bibliothèque qui a fait ça. Cela devrait être votre dernier recours et méfiez-vous des pièges. Souvent, les gens regroupent les dépendances avec leur bibliothèque soit parce qu'ils ciblent un environnement de développement terrible (par exemple, Windows), soit parce qu'ils ne supportent qu'une ancienne version (obsolète) de la bibliothèque (dépendance) en question. Le principal piège est que votre dépendance groupée peut entrer en conflit avec des versions déjà installées de la même bibliothèque / application (par exemple, vous avez groupé gtest, mais la personne essayant de construire votre bibliothèque a déjà une version plus récente (ou plus ancienne) de gtest déjà installée, alors les deux pourraient entrer en conflit et donner à cette personne un mal de tête très méchant). Alors, comme je l'ai dit, fais-le à tes risques et périls, et je dirais qu'en dernier recours. Demander aux gens d'installer quelques dépendances avant de pouvoir compiler votre bibliothèque est un bien moindre mal que d'essayer de résoudre les conflits entre vos dépendances groupées et les installations existantes.
Quote: Quand il s'agit d'écrire des tests, comment sont-ils généralement organisés? Je pensais avoir un fichier cpp pour chaque classe (test_vector3.cpp par exemple) mais tous compilés en un seul binaire afin qu'ils puissent tous être exécutés ensemble facilement?
Un fichier cpp par classe (ou petit groupe cohésif de classes et de fonctions) est à mon avis plus courant et pratique. Cependant, ne les compilez pas tous dans un seul binaire juste pour "qu'ils puissent tous être exécutés ensemble". C'est une très mauvaise idée. En général, quand il s'agit de codage, vous voulez diviser les choses autant qu'il est raisonnable de le faire. Dans le cas des tests unitaires, vous ne voulez pas qu'un binaire exécute tous les tests, car cela signifie que tout petit changement que vous apportez à quoi que ce soit dans votre bibliothèque est susceptible de provoquer une recompilation presque totale de ce programme de test unitaire , et ce ne sont que des minutes / heures perdues en attente de recompilation. Tenez-vous en à un schéma simple: 1 unité = 1 programme de test unitaire. Ensuite,
Quote: Puisque la bibliothèque gtest est généralement construite en utilisant cmake et make, je pensais qu'il serait logique que mon projet soit également construit comme ça? Si j'ai décidé d'utiliser la mise en page de projet suivante:
Je suggérerais plutôt cette mise en page:
trunk
├── bin
├── lib
│ └── project
│ └── libvector3.so
│ └── libvector3.a products of installation / building
├── docs
│ └── Doxyfile
├── include
│ └── project
│ └── vector3.hpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── src
│ └── CMakeLists.txt
│ └── Doxyfile.in
│ └── project part of version-control / source-distribution
│ └── CMakeLists.txt
│ └── vector3.hpp
│ └── vector3.cpp
│ └── test
│ └── test_vector3.cpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── build
└── test working directories for building / testing
└── test_vector3
Quelques points à noter ici. Tout d'abord, les sous-répertoires de votre répertoire src doivent refléter les sous-répertoires de votre répertoire d'inclusion, c'est juste pour garder les choses intuitives (aussi, essayez de garder la structure de votre sous-répertoire raisonnablement plate (superficielle), car l'imbrication profonde des dossiers est souvent plus compliqué qu'autre chose). Deuxièmement, le répertoire "include" n'est qu'un répertoire d'installation, son contenu correspond à tous les en-têtes sélectionnés dans le répertoire src.
Troisièmement, le système CMake est destiné à être distribué sur les sous-répertoires source, et non comme un fichier CMakeLists.txt au niveau supérieur. Cela maintient les choses locales, et c'est bien (dans l'esprit de diviser les choses en morceaux indépendants). Si vous ajoutez une nouvelle source, un nouvel en-tête ou un nouveau programme de test, il vous suffit d'éditer un petit et simple fichier CMakeLists.txt dans le sous-répertoire en question, sans rien affecter d'autre. Cela vous permet également de restructurer facilement les répertoires (les CMakeLists sont locales et contenues dans les sous-répertoires déplacés). Les CMakeLists de niveau supérieur doivent contenir la plupart des configurations de niveau supérieur, telles que la configuration des répertoires de destination, des commandes personnalisées (ou macros) et la recherche de packages installés sur le système. Les CMakeLists de niveau inférieur ne doivent contenir que de simples listes d'en-têtes, de sources,
Quote: Comment le CMakeLists.txt devrait-il ressembler pour qu'il puisse soit construire uniquement la bibliothèque, soit la bibliothèque et les tests?
La réponse de base est que CMake vous permet d'exclure spécifiquement certaines cibles de "tous" (ce qui est construit lorsque vous tapez "make"), et vous pouvez également créer des ensembles spécifiques de cibles. Je ne peux pas faire un didacticiel CMake ici, mais il est assez simple de le découvrir par vous-même. Dans ce cas précis, cependant, la solution recommandée est, bien sûr, d'utiliser CTest, qui est juste un ensemble supplémentaire de commandes que vous pouvez utiliser dans les fichiers CMakeLists pour enregistrer un certain nombre de cibles (programmes) qui sont marquées comme unité- des tests. Donc, CMake placera tous les tests dans une catégorie spéciale de builds, et c'est exactement ce que vous avez demandé, donc le problème est résolu.
Quote: J'ai également vu un certain nombre de projets qui ont une création et un répertoire bin. La construction se produit-elle dans le répertoire de construction, puis les binaires sont-ils déplacés vers le répertoire bin? Les binaires pour les tests et la bibliothèque vivraient-ils au même endroit? Ou serait-il plus logique de le structurer comme suit:
Avoir un répertoire de construction en dehors de la source (version "out-of-source") est vraiment la seule chose sensée à faire, c'est la norme de facto de nos jours. Donc, définitivement, ayez un répertoire "build" séparé, en dehors du répertoire source, tout comme le recommandent les gens de CMake, et comme le font tous les programmeurs que j'ai rencontrés. Quant au répertoire bin, eh 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 cet article.
Quote: Je voudrais également utiliser doxygen pour documenter mon code. Est-il possible que cela s'exécute automatiquement avec cmake et make?
Oui. C'est plus que possible, c'est génial. Selon la fantaisie que vous souhaitez obtenir, il existe plusieurs possibilités. CMake a un module pour Doxygen (c'est-à-dire find_package(Doxygen)
) qui vous permet d'enregistrer des cibles qui exécuteront Doxygen sur certains fichiers. Si vous voulez faire des choses plus sophistiquées, comme mettre à jour le numéro de version dans le Doxyfile, ou entrer automatiquement une date / des timbres d'auteur pour les fichiers source et ainsi de suite, tout est possible avec un peu de CMake kung-fu. Généralement, cela impliquera que vous gardiez un Doxyfile source (par exemple, le "Doxyfile.in" que j'ai mis dans la disposition du dossier ci-dessus) qui a des jetons à trouver et à remplacer par les commandes d'analyse de CMake. Dans mon fichier CMakeLists de niveau supérieur , vous trouverez un tel morceau de CMake kung-fu qui fait quelques choses fantaisistes avec cmake-doxygen ensemble.