webentwicklung-frage-antwort-db.com.de

Das einfachste, aber vollständigste CMake-Beispiel

Irgendwie bin ich total verwirrt, 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 den geringsten Wartungsaufwand erfordert. Zum Beispiel möchte ich meine CMakeList.txt nicht aktualisieren, wenn ich einen neuen Ordner in meinem Quelltextbaum hinzufüge, der genauso funktioniert wie alle anderen Quelltextordner.

So stelle ich mir die Struktur meines Projekts vor, aber bitte, dies ist nur ein Beispiel. Wenn der empfohlene Weg abweicht, sagen Sie mir bitte, und sagen Sie mir, 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 die empfohlene Art des Ressourcenmanagements kennen. Ich möchte nicht mit "../resources/file.png" auf meine Ressourcen zugreifen

107
Arne

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

eine Sache, die nicht dem Standard entspricht, ist die Ressourcenverwaltung. Standardmäßig möchte cmake sie in/usr/share /,/usr/local/share/und etwas Äquivalentes unter Windows ablegen. Ich wollte eine einfache Zip/tar.gz, die 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: <function-name>(<arg1> [<arg2> ...]) ohne Komma oder Semikolor. Jedes Argument ist eine Zeichenfolge. foobar(3.0) und foobar("3.0") sind gleich. Sie können Listen/Variablen mit set(args arg1 arg2) setzen. Mit dieser Variablenmenge sind foobar(${args}) und foobar(arg1 arg2) effektiv 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. Integrierte Funktionen bieten eine Form von benannten Argumenten , da sie in ihren Argumenten einige IDs wie PUBLIC oder DESTINATION erwarten Argumentliste, um die Argumente zu gruppieren. Dies ist jedoch keine Sprachfunktion. Diese IDs sind ebenfalls nur Zeichenfolgen und werden von der Funktionsimplementierung analysiert.

du kannst 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)
83
Arne

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 finden Sie im Lernprogramm eine weitere Möglichkeit: Erstellen Sie eine konfigurierbare Header-Datei, die Sie in Ihren Code einfügen. Erstellen Sie dazu eine Datei configuration.h.in mit folgendem Inhalt:

#define RESOURCES_PATH "@[email protected]"

Dann in Ihrem CMakeLists.txt hinzufügen:

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";
36
sgvd