Einfachstes, aber vollständiges CMake-Beispiel

117

Irgendwie bin ich total verwirrt darüber, wie CMake funktioniert. Jedes Mal, wenn ich denke, dass ich näher komme, um zu verstehen, wie CMake geschrieben werden soll, verschwindet es im nächsten Beispiel, das ich lese. Ich möchte nur wissen, wie ich mein Projekt strukturieren soll, damit mein CMake in Zukunft am wenigsten gewartet werden muss. Zum Beispiel möchte ich meine CMakeList.txt nicht aktualisieren, wenn ich einen neuen Ordner in meinem src-Baum hinzufüge, der genau wie alle anderen src-Ordner funktioniert.

So stelle ich mir die Struktur meines Projekts vor, aber bitte, dies ist nur ein Beispiel. Wenn der empfohlene Weg unterschiedlich ist, sagen Sie es mir bitte und wie es geht.

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

Übrigens ist es wichtig, dass mein Programm weiß, wo sich die Ressourcen befinden. Ich würde gerne wissen, wie Ressourcen empfohlen werden. Ich möchte nicht mit "../resources/file.png" auf meine Ressourcen zugreifen.

Arne
quelle
1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treeKönnen Sie ein Beispiel für eine IDE geben, die automatisch Quellen sammelt?
7
Normalerweise sammeln keine Ideen Quellen nicht automatisch, weil sie es nicht müssen. Wenn ich eine neue Datei oder einen neuen Ordner hinzufüge, mache ich das innerhalb der ide und das Projekt wird aktualisiert. Ein Build-System auf der anderen Seite bemerkt nicht, wenn ich einige Dateien ändere, daher ist es ein gewünschtes Verhalten, dass es alle Quelldateien automatisch sammelt
Arne
4
Wenn ich diesen Link sehe, habe ich den Eindruck, dass CMake bei der wichtigsten Aufgabe, die es lösen wollte, gescheitert ist: Ein plattformübergreifendes Build-System einfach zu machen.
Arne

Antworten:

94

Nach einigen Recherchen habe ich jetzt meine eigene Version des einfachsten, aber vollständigsten cmake-Beispiels. Hier ist es, und es wird versucht, die meisten Grundlagen, einschließlich Ressourcen und Verpackung, abzudecken.

Eine Sache, die nicht dem Standard entspricht, ist das Ressourcenhandling. Standardmäßig möchte cmake sie in / usr / share /, / usr / local / share / und etwas Äquivalentes unter Windows ablegen. Ich wollte ein einfaches zip / tar.gz haben, das Sie überall extrahieren und ausführen können. Daher werden Ressourcen relativ zur ausführbaren Datei geladen.

Die Grundregel zum Verständnis von cmake-Befehlen lautet wie folgt: <function-name>(<arg1> [<arg2> ...])ohne Komma oder Halbfarbe. Jedes Argument ist eine Zeichenfolge. foobar(3.0)und foobar("3.0")ist das gleiche. Sie können Listen / Variablen mit einstellen set(args arg1 arg2). Mit dieser Variablen gesetzt foobar(${args}) und foobar(arg1 arg2)sind praktisch gleich. Eine nicht vorhandene Variable entspricht einer leeren Liste. Eine Liste ist intern nur eine Zeichenfolge mit Semikolons, um die Elemente zu trennen. Daher ist eine Liste mit nur einem Element per Definition nur dieses Element, es findet kein Boxen statt. Variablen sind global. Eingebaute Funktionen bieten eine Form von benannten Argumenten , da sie einige IDs wie oder erwarten in ihrer Argumentliste, um die Argumente zu gruppieren. Dies ist jedoch keine Sprachfunktion. Diese IDs sind auch nur Zeichenfolgen und werden von der Funktionsimplementierung analysiert.PUBLICDESTINATION

Sie können alles von Github klonen

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)
Arne
quelle
8
@SteveLorimer Ich bin nur anderer Meinung, dass das Globbing von Dateien ein schlechter Stil ist. Ich denke, das manuelle Kopieren des Dateibaums in die CMakeLists.txt ist ein schlechter Stil, da er redundant ist. Aber ich weiß, dass die Leute in diesem Thema nicht einverstanden sind, deshalb habe ich einen Kommentar im Code hinterlassen, in dem Sie das Globbing durch eine Liste ersetzen können, die alle Quelldateien explizit enthält. Suche nach set(sources src/main.cpp).
Arne
3
@SteveLorimer ja oft musste ich cmake erneut aufrufen. Jedes Mal, wenn ich etwas in den Verzeichnisbaum einfüge, muss ich cmake manuell neu aufrufen, damit das Globbing neu bewertet wird. Wenn Sie die Dateien in das CMakeLists.txtVerzeichnis einfügen, löst ein normales make (oder ein Ninja) den erneuten Aufruf von cmake aus, sodass Sie es nicht vergessen können. Es ist auch ein bisschen teamfreundlicher, denn dann können die Teammitglieder auch nicht vergessen, cmake auszuführen. Aber ich denke, ein Makefile sollte nicht berührt werden müssen, nur weil jemand eine Datei hinzugefügt hat. Schreiben Sie es einmal, und niemand sollte jemals wieder darüber nachdenken müssen.
Arne
3
@SteveLorimer Ich bin auch nicht einverstanden mit dem Muster, eine CMakeLists.txt in jedes Verzeichnis der Projekte einzufügen. Es streut nur die Konfiguration des Projekts überall. Ich denke, eine Datei, um alles zu tun, sollte ausreichen, sonst verlieren Sie den Überblick über was wird tatsächlich im Build-Prozess durchgeführt. Das bedeutet nicht, dass es keine Unterverzeichnisse mit einer eigenen CMakeLists.txt geben kann. Ich denke nur, dass dies eine Ausnahme sein sollte.
Arne
2
Angenommen, "VCS" ist die Abkürzung für "Versionskontrollsystem" , dann ist das irrelevant. Das Problem ist nicht, dass Artefakte nicht zur Quellcodeverwaltung hinzugefügt werden. Das Problem ist, dass CMake hinzugefügte Quelldateien nicht neu auswerten kann. Build-System-Eingabedateien werden nicht neu generiert. Das Build-System bleibt gerne bei den veralteten Eingabedateien, was entweder zu Fehlern führt (wenn Sie Glück haben) oder unbemerkt bleibt, wenn Sie kein Glück mehr haben. GLOBbing erzeugt eine Lücke in der Abhängigkeitsberechnungskette. Dies ist ein wichtiges Problem, und ein Kommentar erkennt dies nicht angemessen an.
Unsichtbarer
2
CMake und ein VCS arbeiten vollständig isoliert. Das VCS kennt CMake nicht und CMake kennt kein VCS. Es gibt keine Verbindung zwischen ihnen. Es sei denn, Sie schlagen vor, dass Entwickler manuelle Schritte ausführen, Informationen aus dem VCS entfernen und auf einer heuristischen Bereinigung basieren und CMake erneut ausführen sollten. Das skaliert offensichtlich nicht und ist anfällig für den Irrtum, der dem Menschen eigen ist. Nein, sorry, Sie haben bisher noch keinen überzeugenden Punkt für GLOBbing-Dateien gemacht.
Unsichtbarer
39

Das grundlegendste, aber vollständigste Beispiel finden Sie im CMake-Tutorial :

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

Für Ihr Projektbeispiel haben Sie möglicherweise:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

Für Ihre zusätzliche Frage gibt es im Tutorial noch einen Weg: Erstellen Sie eine konfigurierbare Header-Datei, die Sie in Ihren Code aufnehmen. Erstellen Sie dazu eine Dateiconfiguration.h.in mit folgendem Inhalt:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Dann in deinem CMakeLists.txt fügen hinzu:

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

Wenn Sie den Pfad in Ihrem Code benötigen, können Sie Folgendes tun:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";
sgvd
quelle
Vielen Dank, besonders für das RESOURCE_PATH. Irgendwie habe ich nicht verstanden, dass das configure_file das ist, wonach ich gesucht habe. Sie haben jedoch alle Dateien aus dem Projekt manuell hinzugefügt. Gibt es eine bessere Möglichkeit, einfach ein Muster zu definieren, in dem alle Dateien aus dem src-Baum hinzugefügt werden?
Arne
Siehe Dieters Antwort, aber auch meine Kommentare, warum Sie sie nicht verwenden sollten. Wenn Sie es wirklich automatisieren möchten, besteht ein besserer Ansatz darin, ein Skript zu schreiben, das Sie ausführen können, um die Liste der Quelldateien neu zu generieren (oder eine cmake-fähige IDE zu verwenden, die dies für Sie erledigt; ich kenne keine).
SGVD
3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO ist es eine schlechte Idee, den absoluten Pfad zum Quellverzeichnis fest zu codieren . Was ist, wenn Sie Ihr Projekt installieren müssen?
2
Ich weiß, dass das automatische Sammeln von Quellen gut klingt, aber es kann zu allen möglichen Komplikationen führen. In dieser Frage vor einiger Zeit finden Sie eine kurze Diskussion: stackoverflow.com/q/10914607/1401351 .
Peter
2
Sie erhalten genau den gleichen Fehler, wenn Sie cmake nicht ausführen. Das manuelle Hinzufügen von Dateien dauert einmal eine Sekunde, das Ausführen von cmake bei jeder Kompilierung dauert jedes Mal eine Sekunde. Sie brechen tatsächlich ein Merkmal von cmake; Jemand, der am selben Projekt arbeitet und Ihre Änderungen übernimmt, würde Folgendes tun: Läufe machen -> undefinierte Referenzen abrufen -> hoffentlich daran denken, cmake erneut auszuführen, oder Dateien mit Ihnen fehlerhaft ausführen -> cmake ausführen -> make erfolgreich ausführen, während, wenn Sie Dateien hinzufügen von Hand macht er: Läufe machen erfolgreich -> verbringt Zeit mit der Familie. Fassen Sie das zusammen, seien Sie nicht faul und ersparen Sie sich und anderen in Zukunft Kopfschmerzen.
SGVD
2

Hier schreibe ich ein sehr einfaches, aber vollständiges Beispiel für CMakeLists.txt-Dateien.

Quellcode

  1. Tutorials von Hallo Welt zu plattformübergreifendem Android / iOS / Web / Desktop.
  2. Für jede Plattform habe ich eine Beispielanwendung veröffentlicht.
  3. Die Dateistruktur 08-cross_platform wird von meiner Arbeit überprüft
  4. Es ist vielleicht nicht perfekt, aber nützlich und eine bewährte Methode für ein eigenes Team

Danach bot ich ein Dokument für die Details an.

Wenn Sie Fragen haben, können Sie mich kontaktieren und ich würde es gerne erklären.

MinamiTouma
quelle