web-dev-qa-db-fra.com

Exemple le plus simple mais complet de CMake

En quelque sorte, je suis totalement dérouté par le fonctionnement de CMake. Chaque fois que je pense comprendre de plus en plus comment CMake doit être écrit, il disparaît dans l'exemple suivant que je lis. Tout ce que je veux savoir, c'est comment structurer mon projet afin que mon CMake nécessite le moins de maintenance possible dans le futur. Par exemple, je ne souhaite pas mettre à jour mon CMakeList.txt lorsque j'ajoute un nouveau dossier dans mon arborescence src, cela 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 manière recommandée diffère, dites-moi s'il vous plaît, et dites-moi 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ù sont les ressources. J'aimerais connaître le mode de gestion des ressources recommandé. Je ne veux pas accéder à mes ressources avec "../resources/file.png"

107
Arne

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

une des choses non standard est la gestion des ressources. 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 demi-couleur. Chaque argument est une chaîne. foobar(3.0) et foobar("3.0") est identique. vous pouvez définir des listes/variables avec set(args arg1 arg2). Avec cette variable, foobar(${args}) et foobar(arg1 arg2) sont en fait les mêmes. Une variable non existante é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'argument nommé car elles attendent des identifiants tels que PUBLIC ou DESTINATION dans leur liste d'arguments , pour regrouper les arguments. Mais ce n'est pas une fonctionnalité de langage, ces identifiants ne sont que des chaînes et analysés par l'implémentation de la fonction.

vous pouvez tout copier de 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)
83
Arne

L’exemple le plus élémentaire mais complet peut être trouvé dans le tutoriel de 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 compléter votre question, vous pouvez également consulter 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.in avec le contenu suivant:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Puis dans votre CMakeLists.txt ajoutez:

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";
36
sgvd