95 votes

CMake et la recherche d'autres projets et de leurs dépendances

Imaginez le scénario suivant : Project A est une bibliothèque partagée qui a plusieurs dépendances ( LibA , LibB y LibC ). Project B est un exécutable qui a une dépendance à l'égard de project A et nécessite donc tous les Project A Les dépendances de l'utilisateur sont également nécessaires à la construction.

De plus, les deux projets sont construits en utilisant CMake y Project A ne devrait pas avoir besoin d'être installé (via la cible 'install') pour que les services de Project B pour l'utiliser, car cela peut devenir une nuisance pour les développeurs.

Quelle est la meilleure façon de résoudre ces dépendances à l'aide de CMake ? La solution idéale serait aussi simple que possible (mais pas plus) et nécessiterait une maintenance minimale.

164voto

Haroogan Points 6159

Facile. Voici l'exemple qui me vient à l'esprit :

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 sur components/B :

cmake_minimum_required(VERSION 2.8.10)

project(B C 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 sur components/C :

cmake_minimum_required(VERSION 2.8.10)

project(C C 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 sur components/A :

cmake_minimum_required(VERSION 2.8.10)

project(A C 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 sur 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} A C)

Pour que ce soit clair, voici l'arborescence des sources 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

Il existe de nombreux points qui pourraient être adaptés, personnalisés ou modifiés pour satisfaire certains besoins, mais cela devrait au moins vous permettre de commencer.

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

19voto

johnb003 Points 288

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

  1. N'utilisez pas les répertoires include_directories. Au minimum, utilisez target_include_directories . Cependant, vous n'avez probablement pas besoin de le faire si vous utilisez les cibles importées.

  2. Utilisez les cibles importées. 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 d'inclusion, les bibliothèques, etc. Si vous utilisez Boost dans vos en-têtes en B, alors au lieu de PRIVATE, utilisez PUBLIC, et ces dépendances seront ajoutées de manière transitive à tout ce qui dépend de B.

  3. N'utilisez pas de globing de fichier (sauf si vous utilisez la version 3.12). Jusqu'à très récemment, le regroupement de fichiers ne fonctionne que pendant la configuration, donc si vous ajoutez des fichiers et que vous construisez, il ne peut pas détecter les changements jusqu'à ce que vous régénériez explicitement le projet. Cependant, si vous listez les fichiers directement, et essayez de construire, il devrait reconnaître que la configuration n'est pas à jour et régénérer automatiquement dans l'étape de construction.

Il y a de bonnes discussions ici (YouTube) : C++Now 2017 : Daniel Pfeifer "Effective CMake" (en anglais)

Ce qui couvre une idée de gestionnaire de paquets qui permet à votre CMake de niveau racine de travailler avec find_package OU subdirectory Cependant, j'ai essayé d'adopter l'idéologie de ce système et j'ai de gros problèmes avec l'utilisation de find_package pour tout et avoir une structure de répertoire comme la vôtre.

0voto

parasrish Points 2001

Cela peut également être fait en utilisant le CMake Cache mécanisme pour y parvenir (c'est-à-dire le partage des variables spécifiques au projet) :

set (VAR "valeur") CACHE INTERNAL "")

Voir la question de Stack Overflow Comment partager des variables entre différents fichiers CMake .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X