CMake et trouver d’autres projets et leurs dépendances

Imaginez le scénario suivant: Project A est une bibliothèque partagée qui comporte plusieurs dépendances (LibA, LibB, LibC). Le projet B est un exécutable dépendant du projet A et nécessite donc toutes les dépendances du projet A également pour pouvoir être compilé.

De plus, les deux projets sont créés à l’aide de CMake et le projet A ne doit pas nécessairement être installé (via la cible «install») pour que le projet B puisse l’utiliser, car cela peut devenir une nuisance pour les développeurs.

La question est donc la suivante: quelle est la meilleure façon de résoudre ces dépendances avec CMake? La solution idéale serait aussi simple que possible (mais pas plus simple) et nécessiterait une maintenance minimale.

Facile. Voici l’exemple du haut de ma tête:

Le niveau supérieur CMakeLists.txt :

 cmake_minimum_required(VERSION 2.8.10) # You can tweak some common (for all subprojects) stuff here. For example: set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_DISABLE_SOURCE_CHANGES ON) if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(SEND_ERROR "In-source builds are not allowed.") endif () set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_COLOR_MAKEFILE ON) # Remove 'lib' prefix for shared libraries on Windows if (WIN32) set(CMAKE_SHARED_LIBRARY_PREFIX "") endif () # When done tweaking common stuff, configure the components (subprojects). # NOTE: The order matters! The most independent ones should go first. add_subdirectory(components/B) # B is a static library (depends on Boost) add_subdirectory(components/C) # C is a shared library (depends on B and external XXX) add_subdirectory(components/A) # A is a shared library (depends on C and B) add_subdirectory(components/Executable) # Executable (depends on A and C) 

CMakeLists.txt dans les components/B :

 cmake_minimum_required(VERSION 2.8.10) project(BC CXX) find_package(Boost 1.50.0 REQUIRED) file(GLOB CPP_FILES source/*.cpp) include_directories(${Boost_INCLUDE_DIRS}) add_library(${PROJECT_NAME} STATIC ${CPP_FILES}) # Required on Unix OS family to be able to be linked into shared libraries. set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) target_link_libraries(${PROJECT_NAME}) # Expose B's public includes (including Boost transitively) to other # subprojects through cache variable. set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include ${Boost_INCLUDE_DIRS} CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE) 

CMakeLists.txt dans les components/C :

 cmake_minimum_required(VERSION 2.8.10) project(CC CXX) find_package(XXX REQUIRED) file(GLOB CPP_FILES source/*.cpp) add_definitions(${XXX_DEFINITIONS}) # NOTE: Boost's includes are transitively added through B_INCLUDE_DIRS. include_directories(${B_INCLUDE_DIRS} ${XXX_INCLUDE_DIRS}) add_library(${PROJECT_NAME} SHARED ${CPP_FILES}) target_link_libraries(${PROJECT_NAME} B ${XXX_LIBRARIES}) # Expose C's definitions (in this case only the ones of XXX transitively) # to other subprojects through cache variable. set(${PROJECT_NAME}_DEFINITIONS ${XXX_DEFINITIONS} CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE) # Expose C's public includes (including the ones of C's dependencies transitively) # to other subprojects through cache variable. set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include ${B_INCLUDE_DIRS} ${XXX_INCLUDE_DIRS} CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE) 

CMakeLists.txt dans les components/A :

 cmake_minimum_required(VERSION 2.8.10) project(AC CXX) file(GLOB CPP_FILES source/*.cpp) # XXX's definitions are transitively added through C_DEFINITIONS. add_definitions(${C_DEFINITIONS}) # NOTE: B's and Boost's includes are transitively added through C_INCLUDE_DIRS. include_directories(${C_INCLUDE_DIRS}) add_library(${PROJECT_NAME} SHARED ${CPP_FILES}) # You could need `${XXX_LIBRARIES}` here too, in case if the dependency # of A on C is not purely transitive in terms of XXX, but A explicitly requires # some additional symbols from XXX. However, in this example, I assumed that # this is not the case, therefore A is only linked against B and C. target_link_libraries(${PROJECT_NAME} B C) # Expose A's definitions (in this case only the ones of C transitively) # to other subprojects through cache variable. set(${PROJECT_NAME}_DEFINITIONS ${C_DEFINITIONS} CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE) # Expose A's public includes (including the ones of A's dependencies # transitively) to other subprojects through cache variable. set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include ${C_INCLUDE_DIRS} CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE) 

CMakeLists.txt dans les components/Executable :

 cmake_minimum_required(VERSION 2.8.10) project(Executable C CXX) file(GLOB CPP_FILES source/*.cpp) add_definitions(${A_DEFINITIONS}) include_directories(${A_INCLUDE_DIRS}) add_executable(${PROJECT_NAME} ${CPP_FILES}) target_link_libraries(${PROJECT_NAME} AC) 

Pour le clarifier, voici l’arborescence source correspondante:

 Root of the project ├───components │ ├───Executable │ │ ├───resource │ │ │ └───icons │ │ ├───source | | └───CMakeLists.txt │ ├───A │ │ ├───include │ │ │ └───A │ │ ├───source | | └───CMakeLists.txt │ ├───B │ │ ├───include │ │ │ └───B │ │ ├───source | | └───CMakeLists.txt │ └───C │ ├───include │ │ └───C │ ├───source | └───CMakeLists.txt └───CMakeLists.txt 

De nombreux points peuvent être modifiés / personnalisés ou modifiés pour répondre à certains besoins, mais cela devrait au moins vous aider à démarrer.

NOTE: J’ai utilisé cette structure avec succès dans plusieurs projets de taille moyenne et grande.

Alexander Shukaev a pris un bon départ, mais il y a un certain nombre de choses qui pourraient être améliorées:

  1. Ne pas utiliser include_directories. Utilisez au moins target_include_directories . Cependant, vous n’avez probablement même pas besoin de le faire si vous utilisez les cibles imscopes.
  2. Utilisez les cibles imscopes. Exemple pour Boost:

     find_package(Boost 1.56 REQUIRED COMPONENTS date_time filesystem iostreams) add_executable(foo foo.cc) target_link_libraries(foo PRIVATE Boost::date_time Boost::filesystem Boost::iostreams ) 

    Cela prend en charge les répertoires include, les bibliothèques, etc. Si vous avez utilisé Boost dans vos en-têtes dans B, utilisez plutôt PUBLIC à la place de PRIVATE, et ces dépendances seraient ajoutées transitoirement à tout ce qui dépend de B.

  3. N’utilisez pas la globalisation de fichiers (sauf si vous utilisez 3.12). Jusqu’à très récemment, la mise en mémoire des fichiers ne fonctionnait que lors de la configuration. Par conséquent, si vous ajoutez des fichiers et que vous les générez, ils ne peuvent pas détecter les modifications tant que vous n’avez pas régénéré explicitement le projet. Cependant, si vous répertoriez directement les fichiers et tentez de les générer, ils doivent reconnaître que la configuration est obsolète et se régénérer automatiquement à l’étape de la construction.

On parle bien ici (YouTube): C ++ Now 2017: Daniel Pfeifer “Effective CMake”

Qui couvre une idée de gestionnaire de paquets qui permet à votre CMake au niveau racine de fonctionner avec le subdirectorysubdirectory find_package OU, cependant, j’ai essayé d’adopter l’idéologie et j’ai de gros problèmes pour find_package et avoir une structure de répertoire comme la vôtre.

Cela peut également être fait en utilisant le mécanisme CMake Cache pour obtenir le même résultat (c.-à-partage des variables spécifiques au projet):

définir (VAR “valeur” CACHE INTERNAL "")

Reportez-vous à la question sur le débordement de stack Comment partager des variables entre différents fichiers CMake .