CMake: Wie richte ich Quell-, Bibliotheks- und CMakeLists.txt-Abhängigkeiten ein?

Lesezeit: 9 Minuten

CMake Wie richte ich Quell Bibliotheks und CMakeListstxt Abhangigkeiten ein
Florian

Ich habe mehrere Projekte (die alle mit CMake aus derselben Quellbaumstruktur erstellt werden), die alle ihre eigene Mischung aus Dutzenden von unterstützenden Bibliotheken verwenden.

So kam ich auf die Frage, wie man das in CMake richtig einrichtet. Bisher habe ich nur gefunden CMake, wie Abhängigkeiten zwischen Zielen korrekt erstellt werdenaber ich kämpfe immer noch zwischen der Einrichtung von allem mit globalen Abhängigkeiten (die Projektebene weiß alles) oder mit lokalen Abhängigkeiten (jedes untergeordnete Ziel behandelt nur seine eigenen Abhängigkeiten).

Hier ist ein reduziertes Beispiel meiner Verzeichnisstruktur und was ich mir derzeit mit CMake und lokalen Abhängigkeiten ausgedacht habe (das Beispiel zeigt nur ein ausführbares Projekt, App1aber es gibt eigentlich noch mehr, App2, App3etc.):

Lib
+-- LibA
    +-- Inc
        +-- a.h
    +-- Src
        +-- a.cc
    +-- CMakeLists.txt
+-- LibB
    +-- Inc
        +-- b.h
    +-- Src
        +-- b.cc
    +-- CMakeLists.txt
+-- LibC
    +-- Inc
        +-- c.h
    +-- Src
        +-- c.cc
    +-- CMakeLists.txt
App1
+-- Src
    +-- main.cc
+-- CMakeLists.txt

Lib/LibA/CMakeLists.txt

include_directories(Inc ../LibC/Inc)
add_subdirectory(../LibC LibC)
add_library(LibA Src/a.cc Inc/a.h)
target_link_libraries(LibA LibC)

Lib/LibB/CMakeLists.txt

include_directories(Inc)
add_library(LibB Src/b.cc Inc/b.h)

Lib/LibC/CMakeLists.txt

include_directories(Inc ../LibB/Inc)
add_subdirectory(../LibB LibB)
add_library(LibC Src/c.cc Inc/c.h)
target_link_libraries(LibC LibB)

App1/CMakeLists.txt (der Einfachheit halber generiere ich hier die Quell-/Header-Dateien)

cmake_minimum_required(VERSION 2.8)

project(App1 CXX)

file(WRITE "Src/main.cc" "#include \"a.h\"\n#include \"b.h\"\nint main()\n{\na();\nb();\nreturn 0;\n}")
file(WRITE "../Lib/LibA/Inc/a.h" "void a();")
file(WRITE "../Lib/LibA/Src/a.cc" "#include \"c.h\"\nvoid a()\n{\nc();\n}")
file(WRITE "../Lib/LibB/Inc/b.h" "void b();")
file(WRITE "../Lib/LibB/Src/b.cc" "void b() {}")
file(WRITE "../Lib/LibC/Inc/c.h" "void c();")
file(WRITE "../Lib/LibC/Src/c.cc" "#include \"b.h\"\nvoid c()\n{\nb();\n}")

include_directories(
    ../Lib/LibA/Inc
    ../Lib/LibB/Inc
)

add_subdirectory(../Lib/LibA LibA)
add_subdirectory(../Lib/LibB LibB)

add_executable(App1 Src/main.cc)

target_link_libraries(App1 LibA LibB)

Die Bibliotheksabhängigkeiten im obigen Beispiel sehen folgendermaßen aus:

App1 -> LibA -> LibC -> LibB
App1 -> LibB

Im Moment bevorzuge ich die Variante der lokalen Abhängigkeiten, weil sie einfacher zu bedienen ist. Ich gebe nur die Abhängigkeiten auf Quellebene mit an include_directories()auf Linkebene mit target_link_libraries() und auf CMake-Ebene mit add_subdirectory().

Damit müssen Sie die Abhängigkeiten zwischen den unterstützenden Bibliotheken nicht kennen und – mit dem CMake-Level “includes” – landen Sie nur bei den Zielen, die Sie wirklich verwenden. Natürlich könnten Sie einfach alle Include-Verzeichnisse und Ziele global bekannt machen und den Compiler/Linker den Rest erledigen lassen. Aber das scheint mir eine Art Blähungen zu sein.

Ich habe auch versucht, eine zu haben Lib/CMakeLists.txt um alle Abhängigkeiten in der zu handhaben Lib Verzeichnisbaum, aber am Ende hatte ich eine Menge if ("${PROJECT_NAME}" STREQUAL ...) Prüfungen und das Problem, dass ich keine Zwischenbibliotheken erstellen kann, die Ziele gruppieren, ohne mindestens eine Quelldatei anzugeben.

Das obige Beispiel ist also “so weit, so gut”, aber es wirft den folgenden Fehler, weil Sie a nicht hinzufügen sollten/können CMakeLists.txt zweimal:

CMake Error at Lib/LibB/CMakeLists.txt:2 (add_library):
  add_library cannot create target "LibB" because another target with the
  same name already exists.  The existing target is a static library created
  in source directory "Lib/LibB".
  See documentation for policy CMP0002 for more details.

Im Moment sehe ich zwei Lösungen dafür, aber ich denke, ich habe das viel zu kompliziert gemacht.

1. Überschreiben add_subdirectory() um Duplikate zu vermeiden

function(add_subdirectory _dir)
    get_filename_component(_fullpath ${_dir} REALPATH)
    if (EXISTS ${_fullpath} AND EXISTS ${_fullpath}/CMakeLists.txt)
        get_property(_included_dirs GLOBAL PROPERTY GlobalAddSubdirectoryOnceIncluded)
        list(FIND _included_dirs "${_fullpath}" _used_index)
        if (${_used_index} EQUAL -1)
            set_property(GLOBAL APPEND PROPERTY GlobalAddSubdirectoryOnceIncluded "${_fullpath}")
            _add_subdirectory(${_dir} ${ARGN})
        endif()
    else()
        message(WARNING "add_subdirectory: Can't find ${_fullpath}/CMakeLists.txt")
    endif()
endfunction(add_subdirectory _dir)

2. Hinzufügen eines “include guard” zu allen Unterebenen CMakeLists.txts, wie:

if (NOT TARGET LibA)
    ...
endif()

Ich habe die von tamas.kenez und ms vorgeschlagenen Konzepte mit einigen vielversprechenden Ergebnissen getestet. Die Zusammenfassungen finden Sie in meinen folgenden Antworten:

  • bevorzugte cmake-Projektstruktur
  • CMake-Freigabebibliothek mit mehreren ausführbaren Dateien
  • Automatisches Zugreifen auf die cmake-Bibliothek durch andere cmake-Pakete

  • Ich sehe den Grund dafür nicht add_subdirectory(../Lib/LibA LibA) innerhalb App1/CMakeLists.txt. Warum brauchen Sie das? Normalerweise haben Sie eine Wurzel CMakeLists.txt die die Unterverzeichnisse hinzufügt.

    – Frau

    20. Juli 2015 um 9:33 Uhr

  • @ms In meinem realen Projekt gibt es mehr Anwendungen als nur meine App1 (Ich habe der Frage einige Details dazu hinzugefügt). Bisher habe ich keine Root-Ebene hinzugefügt CMakeLists.txt um eine Abhängigkeit zwischen den verschiedenen arbeitenden Projektteams zu vermeiden App1, App2, etc (und ich konnte mir keine vorstellen, die auf der einen Seite generisch genug für alle Projekte und auf der anderen Seite spezifisch genug ist, dass sie am Ende nur ein ausführbares Ziel generiert). Im Moment gehen sie einfach in ihr Unterverzeichnis, erstellen ein binäres Unterverzeichnis, generieren ihre Make-Umgebung mit cmake .. und Ruf an make.

    – Florian

    20. Juli 2015 um 10:25 Uhr


  • Wie wäre es mit hinzufügen LibA, LibBusw. durch ExternalProject_Add beim Weiterarbeiten App1?

    – Frau

    20. Juli 2015 um 10:29 Uhr

  • @ms In meinem App1 Beispiel, wenn ich beide hinzufüge LibA und LibB über externe Projekte, würde ich nicht mit doppelten Symbolen für alles, was drin ist, enden LibB?

    – Florian

    20. Juli 2015 um 10:34 Uhr

  • schau dir hier die Artikelserie an: coderwall.com/p/qk2eog/…

    – Frau

    20. Juli 2015 um 10:36 Uhr

Das mehrfache Hinzufügen desselben Unterverzeichnisses steht außer Frage, es ist nicht so, wie CMake funktionieren soll. Es gibt zwei Hauptalternativen, um dies auf saubere Weise zu tun:

  1. Erstellen Sie Ihre Bibliotheken im selben Projekt wie Ihre App. Bevorzugen Sie diese Option für Bibliotheken, an denen Sie aktiv arbeiten (während Sie an der App arbeiten), damit sie wahrscheinlich häufig bearbeitet und neu erstellt werden. Sie werden auch im selben IDE-Projekt angezeigt.

  2. Erstellen Sie Ihre Bibliotheken in einem externen Projekt (und ich meine nicht ExternalProject). Bevorzugen Sie diese Option für Bibliotheken, die nur von Ihrer App verwendet werden, an denen Sie jedoch nicht arbeiten. Dies ist bei den meisten Bibliotheken von Drittanbietern der Fall. Sie werden auch Ihren IDE-Arbeitsbereich nicht überladen.

Methode 1

  • Ihrer App CMakeLists.txt fügt die Unterverzeichnisse der Bibliotheken (und Ihrer libs) hinzu CMakeLists.txtnicht)
  • Ihrer App CMakeLists.txt ist dafür verantwortlich, alle unmittelbaren und transitiven Abhängigkeiten in der richtigen Reihenfolge hinzuzufügen
  • Es wird davon ausgegangen, dass das Hinzufügen des Unterverzeichnisses für libx wird ein Ziel erstellen (z libx), die problemlos verwendet werden können target_link_libraries

Als Nebenbemerkung: Für die Bibliotheken ist es eine gute Praxis, eine voll funktionsfähig Bibliotheksziel, d. h. eines, das alle Informationen enthält, die zur Verwendung der Bibliothek erforderlich sind:

add_library(LibB Src/b.cc Inc/b.h)
target_include_directories(LibB PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>)

So kann der Ort von Include-Verzeichnissen der Bibliothek eine interne Angelegenheit der Bibliothek bleiben. Sie müssen nur dies tun;

target_link_libraries(LibC LibB)

dann die Include-Verzeichnisse von LibB wird ebenfalls in die Zusammenstellung von aufgenommen LibC. Verwenden Sie die PRIVATE Modifikator wenn LibB wird von den öffentlichen Headern von nicht verwendet LibC:

target_link_libraries(LibC PRIVATE LibB)

Methode Nr. 2

Erstellen und installieren Sie Ihre Bibliotheken in separaten CMake-Projekten. Ihre Bibliotheken installieren eine sogenannte config-modul die die Speicherorte der Header- und Bibliotheksdateien sowie Kompilier-Flags beschreibt. Ihre App CMakeList.txt setzt voraus, dass die Bibliotheken bereits erstellt und installiert wurden und die Konfigurationsmodule von der gefunden werden können find_package Befehl. Das ist eine ganz andere Geschichte, also werde ich hier nicht ins Detail gehen.

Ein paar Anmerkungen:

  • Sie können Nr. 1 und Nr. 2 mischen, da Sie in den meisten Fällen sowohl unveränderliche Bibliotheken von Drittanbietern als auch Ihre eigenen Bibliotheken in der Entwicklung haben.
  • Ein Kompromiss zwischen Nr. 1 und Nr. 2 ist die Verwendung von ExternalProject-Modul, von vielen bevorzugt. Es ist, als würden Sie die externen Projekte Ihrer Bibliotheken (die in ihrem eigenen Build-Baum erstellt wurden) in das Projekt Ihrer App aufnehmen. In gewisser Weise kombiniert es die Nachteile beider Ansätze: Sie können Ihre Bibliotheken nicht als Ziele verwenden (weil sie sich in einem anderen Projekt befinden) und Sie können sie nicht aufrufen find_package (weil die Bibliotheken nicht zu der Zeit installiert sind, zu der Ihre App installiert ist CMakeLists konfiguriert).
  • Eine Variante von Nr. 2 besteht darin, die Bibliothek in einem externen Projekt zu erstellen, aber anstatt die Artefakte zu installieren, verwenden Sie sie von ihren Quell-/Build-Speicherorten. Mehr dazu siehe unter export() Befehl.

  • Methode 1: Ich mag Ihre Verwendung der target_include_directories() Befehl (CMake >= 2.8.12), es macht die Dinge definitiv einfacher. Aber ich suche nach einer Lösung, bei der der Benutzer der Bibliotheken die internen Abhängigkeiten nicht kennen muss. Methode2: Ich werde einen Blick auf die Verwendung der binären Zustellung werfen und die find_package() Befehl. Könnt ihr nützliche Links empfehlen?

    – Florian

    21. Juli 2015 um 7:10 Uhr

  • Hinsichtlich der ExternalProject Modul: Ich denke, der Befehl ExternalProject_Add() ist eine Möglichkeit, wenn wir über größere monolithische Bibliotheken sprechen. Aber für meine über 50 kleineren Bibliotheken denke ich nicht, dass dies eine Option ist, und bis zu einem gewissen Grad gilt das Argument, dass es nicht so verwendet werden soll, auch hier. Der Overhead ist einfach zu groß: zB mache ich mir Sorgen, dass es – wenn ich das nutzen würde SOURCE_DIR Option – würde die Compiler-Erkennung für jede Bibliothek starten und ich muss alle Build-Umgebungs-spezifischen Optionen inkl. meine Toolchain-Datei.

    – Florian

    21. Juli 2015 um 7:15 Uhr

  • @Florian, für Methode2 habe ich eine Tutorial-Frage erstellt: stackoverflow.com/questions/31537602/… . Es deckt nur eine einzige Bibliothek ohne Abhängigkeiten ab. Ich kann in den nächsten Tagen ein neues Tutorial hinzufügen, das Abhängigkeiten einführt. Oder Sie können auch eine neue Frage dazu stellen.

    – tamas.kenez

    21. Juli 2015 um 11:28 Uhr

  • @Florian, ExternalProject: Sie haben Recht mit dem Overhead. Aus den anderen genannten Gründen verwende ich es auch nicht.

    – tamas.kenez

    21. Juli 2015 um 11:30 Uhr


  • @Florian, Verwaltung von über 50 kleinen Bibliotheken: Um dies effektiv zu tun, benötigen Sie eine zusätzliche Infrastruktur, z. B. eine Reihe benutzerdefinierter oder generischer Shell-Skripts. Viele Leute haben ganze Frameworks darum herum erstellt (biicode, cmakepp, CPM, fips, hunter). Ich habe einen CMake-basierten Paket-/Repo-Manager für mich selbst geschrieben.

    – tamas.kenez

    21. Juli 2015 um 11:35 Uhr

916400cookie-checkCMake: Wie richte ich Quell-, Bibliotheks- und CMakeLists.txt-Abhängigkeiten ein?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy