Imaginons le scénario suivant: Le projet A est une bibliothèque partagée comportant plusieurs dépendances (LibA, LibB, LibC). Le projet B est un exécutable qui dépend du projet A et nécessite donc toutes les dépendances du projet A également pour pouvoir être construit.
De plus, les deux projets sont construits en utilisant CMake, et le projet A ne devrait pas avoir besoin d'être installé (via la cible 'install') pour que le projet B puisse l'utiliser, car cela pourrait devenir une nuisance pour les développeurs.
La question est donc: quel est le meilleur moyen de résoudre ces dépendances en utilisant CMake? La solution idéale serait aussi simple que possible (mais pas plus simple), et nécessiterait un minimum de maintenance.
Facile. Voici l'exemple du haut de ma tête:
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 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
dans 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
dans 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
dans 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 clarifier, voici la structure de 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
Il existe de nombreux points où cela pourrait être modifié/personnalisé ou modifié pour satisfaire certains besoins, mais cela devrait au moins vous aider à démarrer.
NOTE: J'ai utilisé cette structure avec succès dans plusieurs projets de moyenne et grande envergure.
Alexander Shukaev a pris un bon départ, mais plusieurs choses pourraient être améliorées:
target_include_directories
. Cependant, vous n'avez probablement même pas besoin de le faire si vous utilisez les cibles importées.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 s’occupe des répertoires d’inclusion, des bibliothèques, etc. Si vous avez utilisé Boost dans vos en-têtes de B, utilisez plutôt PUBLIC au lieu de PRIVATE, et ces dépendances seraient ajoutées de manière transitoire à tout ce qui dépend de B.
N'utilisez pas de fichier globing (sauf si vous utilisez 3.12). Jusqu'à tout récemment, la segmentation de fichiers ne fonctionnait que pendant la configuration. Par conséquent, si vous ajoutez des fichiers et les construisez, les modifications ne seront pas détectées tant que vous ne aurez pas régénéré explicitement le projet. Toutefois, si vous répertoriez directement les fichiers et tentez de les compiler, le système doit reconnaître que la configuration est obsolète et se régénérer automatiquement à l'étape de compilation.
On parle bien ici (YouTube): C++ Now 2017: Daniel Pfeifer “Effective CMake"
Ce qui couvre une idée de gestionnaire de paquets qui permet à votre CMake de niveau racine de travailler avec find_package
OR subdirectory
, cependant, j'ai essayé d'adopter la même idéologie et j'ai de gros problèmes à utiliser find_package
pour tout et avoir un répertoire structure comme la vôtre.
Cela peut également être fait en utilisant le mécanisme CMon Cache
pour obtenir le même résultat (partage de variables spécifiques à un projet):
set (VAR "valeur"
CACHE INTERNAL "")
Reportez-vous à la question relative au débordement de pile Comment partager des variables entre différents fichiers CMake.