it-swarm.com.de

CMake und finden Sie andere Projekte und ihre Abhängigkeiten

Stellen Sie sich folgendes Szenario vor: Projekt A ist eine gemeinsam genutzte Bibliothek mit mehreren Abhängigkeiten (LibA, LibB, LibC). Projekt B ist eine ausführbare Datei, die von Projekt A abhängig ist und daher auch alle Abhängigkeiten von Projekt A benötigt, um zu erstellen.

Außerdem werden beide Projekte mit CMake erstellt. Projekt A sollte nicht installiert werden (über das Installationsziel), damit Project B es verwenden kann, da dies für Entwickler ärgerlich werden kann.

Die Frage ist also: Wie kann man diese Abhängigkeiten mit CMake am besten lösen? Die ideale Lösung wäre so einfach wie möglich (wenn auch nicht einfacher) und erfordert nur minimale Wartung.

63
blockchaindev

Einfach. Hier ist das Beispiel aus meinem Kopf:

Die oberste Ebene 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 in 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 in 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 in 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 in 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)

Um es klar zu machen, hier ist die entsprechende Quellbaumstruktur:

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

Es gibt viele Punkte, an denen dies angepasst/angepasst oder geändert werden kann, um bestimmte Anforderungen zu erfüllen. Dies sollte jedoch zumindest den Einstieg erleichtern.

HINWEIS: Ich habe diese Struktur in mehreren mittelgroßen und großen Projekten erfolgreich eingesetzt.

120

Alexander Shukaev hat einen guten Start, aber es gibt einige Dinge, die man besser machen könnte:

  1. Verwenden Sie keine include_directories. Verwenden Sie zumindest target_include_directories. Dies ist jedoch wahrscheinlich nicht erforderlich, wenn Sie die importierten Ziele verwenden.
  2. Verwenden Sie die importierten Ziele. Beispiel für 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
    )
    

    Dadurch werden die Include-Verzeichnisse, Bibliotheken usw. berücksichtigt. Wenn Sie Boost in Ihren Kopfzeilen in B verwenden, verwenden Sie PUBLIC anstelle von PRIVATE, und diese Abhängigkeiten werden transitiv zu dem von B abhängigen Element hinzugefügt.

  3. Verwenden Sie keine Dateiauswahl (es sei denn, Sie verwenden 3.12). Bis vor kurzem funktioniert das Globieren von Dateien nur während der Konfigurationszeit. Wenn Sie also Dateien hinzufügen und erstellen, können die Änderungen erst erkannt werden, wenn Sie das Projekt explizit neu erstellen. Wenn Sie die Dateien jedoch direkt auflisten und versuchen, zu erstellen, sollte sie erkennen, dass die Konfiguration nicht mehr aktuell ist, und im Erstellungsschritt automatisch neu erstellt werden.

Es gibt ein gutes Gespräch hier (YouTube): C++ Now 2017: Daniel Pfeifer "Effective CMake"

Was die Idee eines Package Managers betrifft, die es Ihrem Root-Level-CMake ermöglicht, mit find_package OR subdirectory zusammenzuarbeiten, habe ich versucht, die Ideologie davon zu übernehmen und habe große Probleme mit find_package für alles und ein Verzeichnis Struktur wie deine.

6
johnb003

Dies kann auch mit dem CMake-Mechanismus Cache durchgeführt werden, um dasselbe zu erreichen (d. H. Das Teilen projektspezifischer Variablen):

set (VAR "value" CACHE INTERNAL "")

Siehe Stapelüberlauf-Frage So teilen Sie Variablen zwischen verschiedenen CMake-Dateien.

0
parasrish