CMake und Suche nach anderen Projekten und deren Abhängigkeiten

76

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

Darüber hinaus werden beide Projekte mit CMake erstellt, und Projekt A sollte nicht installiert werden müssen (über das Installationsziel), damit Projekt B es verwenden kann, da dies für Entwickler zu einem Ärgernis werden kann.

Was ist der beste Weg, um diese Abhängigkeiten mit CMake zu lösen? Die ideale Lösung wäre so einfach wie möglich (wenn auch nicht einfacher) und erfordert nur minimale Wartung.

blockchaindev
quelle
2
Für zukünftigen Wohlstand: cmake.org/Wiki/CMake/Tutorials/...
blockchaindev
1
Dieses Tutorial scheint nicht zu erklären, wie man mit dem Exportieren externer Bibliotheksabhängigkeiten umgeht. Die einzige Bibliothek, mit der verknüpft ist, ist eine vom Projekt erstellte. Ich muss wissen, wie ich Projekt B mitteilen kann, dass für Projekt A verschiedene externe Bibliotheken erforderlich sind, und diese müssen daher zum Verknüpfungsschritt von Projekt B hinzugefügt werden.
Ben Farmer
Eigentlich sollten Sie Linux oder Linux-Subsystem ausprobieren, wenn Sie ein PC-Typ sind. Das Beste an dieser Plattform ist, dass Linux alle Abhängigkeiten für Sie installiert. Oder noch besser, es schlägt vor, welche Abhängigkeiten Ihnen fehlen, und bietet Sudo apt-get install mydependencies, wie zu installieren. Wirklich einfach.
Juniar
@Juniar, das vereinfacht und optimiert die Dinge sehr, da stimme ich zu. Aber macht die Bereitstellung von Software zu einem Albtraum. Ich würde es vorziehen, alles in einem Paket für meine Software zu haben und alles zusammen bereitzustellen (sogar einige Bibliotheken teilweise zu duplizieren). Ganz zu schweigen von den Wartungsproblemen. Jede Box verfügt über einen eindeutigen Satz von Bibliotheken (bis zu einem gewissen Grad).
OpalApps
@OpalApps, Die Abhängigkeiten werden möglicherweise auf verschiedenen Pfaden und Verzeichnissen installiert. Sie können diese Abhängigkeiten jedoch weiterhin zur Kompilierungszeit hinzufügen oder externe Pfade konfigurieren / einschließen. Sie werden nicht alle auf einem Pfad installiert. Richtig, "sudo apt-get install" wird jedoch in bestimmten Verzeichnissen installiert. Wechseln Sie sie einfach um.
Juniar

Antworten:

147

Einfach. Hier ist das Beispiel von oben:

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.txtin 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.txtin 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.txtin 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.txtin 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 dies zu verdeutlichen, ist hier 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 optimiert / angepasst oder geändert werden kann, um bestimmte Anforderungen zu erfüllen, aber dies sollte Ihnen zumindest den Einstieg erleichtern.

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

Alexander Shukaev
quelle
15
Du bist ein F **** STAR! Du hast mich wirklich aus massiven Kopfschmerzen herausgeholt, die fast einen Tag dauerten. Vielen Dank 1
rsacchettini
2
Ich bin neugierig, würde es kompilieren, wenn ich das Cmake aus dem Verzeichnis "Executable" aufrufen würde; oder sollte ich jederzeit aus dem Stammverzeichnis des Projekts kompilieren?
Ahmet Ipkin
1
Ich denke, es hat den kleinen Nachteil, dass Sie in jedem Projekt die Include-Verzeichnisse zweimal definieren (einmal für set(...INCLUDE_DIRSund einmal für include_directories()), was ich schwer zu pflegen finde (immer daran denken, an zwei Stellen eine neue Include-Abhängigkeit hinzuzufügen ). Sie könnten sie mit abfragen get_property(...PROPERTY INCLUDE_DIRECTORIES).
Pseyfert
2
Das ist wirklich großartig, aber wie kann das Gleiche erreicht werden, wenn A, B und C vollständig getrennte Projekte sind? Dh ich möchte A erstellen und exportieren, um eine eigene ProjectConfig.cmake-Datei zu haben, und dann in B find_package verwenden, um A auf dem System zu finden, und irgendwie eine Liste aller Bibliotheken erhalten, von denen A abhängt, damit sie wann verknüpft werden können Gebäude B.
Ben Farmer
2
@ Ben Farmer, dies ist in der Tat ein komplizierteres Thema, das einen großen Artikel verdient, um es richtig zu erklären. Ich hatte nie genug Geduld, um es hier zu skizzieren. Genau das tue ich, um meine Projekte zu verwalten, da dies die ultimative (professionelle) Verwendung und Absicht von CMake ist. Um all das zu verwalten, habe ich mein eigenes CMake-Framework, das viel hinter den Kulissen handhabt. Als Beispiel könnten Sie versuchen, eines meiner C ++ Hacks- oder C ++ Firewall- Spielzeugprojekte zu erstellen.
Alexander Shukaev
15

Alexander Shukaev hat einen großartigen Start hingelegt, aber es gibt eine Reihe von Dingen, die besser gemacht werden könnten:

  1. Verwenden Sie keine include_directories. Zumindest verwenden target_include_directories. Sie müssen dies jedoch wahrscheinlich nicht einmal tun, 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
    )
    

    Dies kümmert sich um die Include-Verzeichnisse, Bibliotheken usw. Wenn Sie Boost in Ihren Headern in B verwendet haben, verwenden Sie anstelle von PRIVATE PUBLIC, und diese Abhängigkeiten werden transitiv zu dem hinzugefügt, was von B abhängt.

  3. Verwenden Sie kein Dateiglobing (es sei denn, Sie verwenden 3.12). Bis vor kurzem funktioniert das Globbing 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 generieren. Wenn Sie die Dateien jedoch direkt auflisten und versuchen, sie zu erstellen, sollte sie erkennen, dass die Konfiguration veraltet ist, und im Erstellungsschritt automatisch neu generiert werden.

Hier (YouTube) wird gut geredet: C ++ Now 2017: Daniel Pfeifer „Effective CMake“

Welche einen Paket - Manager Idee deckt , dass Ihr Root - Ebene CMake Arbeit mit ermöglicht find_packageOR subdirectory, aber ich habe versucht , die Ideologie , dies zu übernehmen und große Probleme habe mit der Verwendung find_packagefür alles und eine Verzeichnisstruktur wie das Ihre ist.

johnb003
quelle