Exemple de CMake le plus simple mais complet

117

D'une certaine manière, je suis totalement confus par le fonctionnement de CMake. Chaque fois que je pense que je me rapproche pour comprendre comment CMake est censé être écrit, cela disparaît dans l'exemple suivant que je lis. Tout ce que je veux savoir, c'est comment structurer mon projet pour que mon CMake nécessite le moins de maintenance à l'avenir. Par exemple, je ne veux pas mettre à jour mon CMakeList.txt lorsque j'ajoute un nouveau dossier dans mon arborescence src, qui fonctionne exactement comme tous les autres dossiers src.

C'est ainsi que j'imagine la structure de mon projet, mais s'il vous plaît, ce n'est qu'un exemple. Si la méthode recommandée diffère, veuillez me le dire et me dire comment le faire.

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

En passant, il est important que mon programme sache où se trouvent les ressources. J'aimerais connaître la manière recommandée de gérer les ressources. Je ne souhaite pas accéder à mes ressources avec "../resources/file.png"

Arne
la source
1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treepouvez-vous donner un exemple d'IDE qui collecte automatiquement les sources?
7
aucun ide ne collecte normalement les sources automatiquement, car ils n'en ont pas besoin. Lorsque j'ajoute un nouveau fichier ou dossier, je le fais dans l'ide et le projet est mis à jour. Un système de construction de l'autre côté ne remarque pas quand je change certains fichiers, c'est donc un comportement souhaité qu'il collecte automatiquement tous les fichiers source
Arne
4
Si je vois ce lien, j'ai l'impression que CMake a échoué dans la tâche la plus importante qu'il voulait résoudre: rendre facile un système de construction multiplateforme.
Arne

Réponses:

94

après quelques recherches, j'ai maintenant ma propre version de l'exemple de cmake le plus simple mais complet. Le voici, et il essaie de couvrir la plupart des bases, y compris les ressources et l'emballage.

la gestion des ressources est une chose non standard. Par défaut, cmake veut les mettre dans / usr / share /, / usr / local / share / et quelque chose d'équivalent sous Windows. Je voulais avoir un simple zip / tar.gz que vous pouvez extraire n'importe où et exécuter. Par conséquent, les ressources sont chargées par rapport à l'exécutable.

la règle de base pour comprendre les commandes cmake est la syntaxe suivante: <function-name>(<arg1> [<arg2> ...])sans virgule ni point-virgule. Chaque argument est une chaîne. foobar(3.0)et foobar("3.0")c'est pareil. vous pouvez définir des listes / variables avec set(args arg1 arg2). Avec cet ensemble de variables foobar(${args}) et foobar(arg1 arg2)sont effectivement les mêmes. Une variable inexistante équivaut à une liste vide. Une liste est en interne juste une chaîne avec des points-virgules pour séparer les éléments. Par conséquent, une liste avec un seul élément est par définition juste cet élément, aucune boxe n'a lieu. Les variables sont globales. Les fonctions intégrées offrent une forme d' arguments nommés du fait qu'elles attendent des identifiants comme PUBLICouDESTINATIONdans leur liste d'arguments, pour regrouper les arguments. Mais ce n'est pas une fonctionnalité de langage, ces identifiants ne sont également que des chaînes et analysés par l'implémentation de la fonction.

vous pouvez tout cloner depuis github

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)
Arne
la source
8
@SteveLorimer Je ne suis pas d'accord, ce fichier globbing est un mauvais style, je pense que copier manuellement l'arborescence de fichiers dans CMakeLists.txt est un mauvais style car il est redondant. Mais je sais que les gens ne sont pas d'accord sur ce sujet, j'ai donc laissé un commentaire dans le code, où vous pouvez remplacer le globbing par une liste contenant tous les fichiers sources de manière explicite. Recherchez set(sources src/main.cpp).
Arne
3
@SteveLorimer oui, j'ai souvent dû appeler à nouveau cmake. Chaque fois que j'ajoute quelque chose dans l'arborescence de répertoires, je dois réinvoquer cmake manuellement, de sorte que le globbing soit réévalué. Si vous mettez les fichiers dans le CMakeLists.txt, alors un make normal (ou ninja) déclenchera la réinvocation de cmake, donc vous ne pouvez pas l'oublier. C'est aussi un peu plus convivial pour l'équipe, car les membres de l'équipe ne peuvent pas non plus oublier d'exécuter cmake. Mais je pense qu'un makefile ne devrait pas avoir besoin d'être touché, juste parce que quelqu'un a ajouté un fichier. Écrivez-le une fois, et personne ne devrait plus avoir à y penser.
Arne
3
@SteveLorimer Je suis également en désaccord avec le modèle pour mettre un CMakeLists.txt dans chaque répertoire des projets, cela disperse simplement la configuration du projet partout, je pense qu'un fichier pour tout faire devrait suffire, sinon vous perdez la vue d'ensemble, de quoi est en fait fait dans le processus de construction. Cela ne signifie pas qu'il ne peut pas y avoir de sous-répertoires avec leur propre CMakeLists.txt, je pense juste que cela devrait être une exception.
Arne
2
En supposant que «VCS» est l'abréviation de «système de contrôle de version» , cela n'a pas d'importance. Le problème n'est pas que les artefacts ne seront pas ajoutés au contrôle de code source. Le problème est que CMake ne réévaluera pas les fichiers source ajoutés. Il ne régénérera pas les fichiers d'entrée du système de construction. Le système de construction s'en tiendra volontiers aux fichiers d'entrée obsolètes, ce qui entraînera des erreurs (si vous avez de la chance), ou passe inaperçu, si vous manquez de chance. GLOBbing produit un vide dans la chaîne de calcul des dépendances. C'est une question importante, et un commentaire ne reconnaît pas correctement cela.
IInspectable
2
CMake et un VCS fonctionnent de manière totalement isolée. Le VCS n'est pas au courant de CMake et CMake n'est au courant d'aucun VCS. Il n'y a aucun lien entre eux. À moins que vous ne suggériez que les développeurs prennent des mesures manuelles, en retirant les informations du VCS, et en se basant sur une certaine heuristique de nettoyage et de réexécution de CMake. Cela n'est évidemment pas à l'échelle et est susceptible de l'erreur qui est propre aux humains. Non, désolé, vous n'avez pas encore fait valoir de points convaincants pour les fichiers GLOBbing.
IInspectable
39

L'exemple le plus basique mais complet se trouve dans le didacticiel CMake :

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

Pour votre exemple de projet, vous pouvez avoir:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

Pour votre question supplémentaire, une façon de procéder est à nouveau dans le didacticiel: créez un fichier d'en-tête configurable que vous incluez dans votre code. Pour cela, créez un fichier configuration.h.inavec le contenu suivant:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Puis dans votre CMakeLists.txtajout:

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

Enfin, là où vous avez besoin du chemin dans votre code, vous pouvez faire:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";
sgvd
la source
merci beaucoup, en particulier pour le RESOURCE_PATH, en quelque sorte je n'ai pas compris que le configure_file est ce que je cherchais. Mais vous avez ajouté manuellement tous les fichiers du projet, existe-t-il un meilleur moyen de définir simplement un modèle dans lequel tous les fichiers sont ajoutés à partir de l'arborescence src?
Arne
Voir la réponse de Dieter, mais aussi mes commentaires sur les raisons pour lesquelles vous ne devriez pas l'utiliser. Si vous voulez vraiment l'automatiser, une meilleure approche peut être d'écrire un script que vous pouvez exécuter pour régénérer la liste des fichiers source (ou utiliser un IDE compatible cmake qui le fait pour vous; je ne connais aucun).
sgvd
3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO c'est une mauvaise idée de coder en dur le chemin absolu vers le répertoire source. Et si vous devez installer votre projet?
2
Je sais que la collecte automatique de sources sonne bien, mais cela peut entraîner toutes sortes de complications. Voir cette question il y a quelque temps pour une brève discussion: stackoverflow.com/q/10914607/1401351 .
Peter
2
Vous obtenez exactement la même erreur si vous n'exécutez pas cmake; l'ajout de fichiers à la main prend une seconde une fois, l'exécution de cmake à chaque compilation prend une seconde à chaque fois; vous cassez en fait une fonction de cmake; quelqu'un qui travaille sur le même projet et récupère vos modifications ferait: exécute make -> récupère des références non définies -> j'espère ne pas oublier de réexécuter cmake, ou les fichiers bogue avec vous -> exécute cmake -> exécute make avec succès, alors que si vous ajoutez un fichier à la main il fait: court faire avec succès -> passe du temps en famille. Résumez cela, ne soyez pas paresseux et épargnez-vous et aux autres un mal de tête à l'avenir.
sgvd
2

Ici, j'écris un exemple de fichiers CMakeLists.txt le plus simple mais complet.

Code source

  1. Tutoriels de hello world à multiplateforme Android / iOS / Web / Desktop.
  2. Chaque plate-forme j'ai publié un exemple d'application.
  3. La structure du fichier 08-cross_platform est vérifiée par mon travail
  4. Ce n'est peut-être pas parfait mais utile et les meilleures pratiques pour une équipe seule

Après cela, j'ai offert un document pour les détails.

Si vous avez des questions, vous pouvez me contacter et j'aimerais vous l'expliquer.

MinamiTouma
la source