Esempio CMake più semplice ma completo


117

In qualche modo sono totalmente confuso da come funziona CMake. Ogni volta che penso che mi sto avvicinando per capire come dovrebbe essere scritto CMake, svanisce nel prossimo esempio che leggo. Tutto quello che voglio sapere è come dovrei strutturare il mio progetto, in modo che il mio CMake richieda la minima manutenzione in futuro. Ad esempio, non voglio aggiornare il mio CMakeList.txt quando aggiungo una nuova cartella nel mio albero src, che funziona esattamente come tutte le altre cartelle src.

Ecco come immagino la struttura del mio progetto, ma per favore questo è solo un esempio. Se il modo consigliato è diverso, dimmelo e dimmi come farlo.

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

A proposito, è importante che il mio programma sappia dove sono le risorse. Vorrei conoscere il modo consigliato di gestire le risorse. Non voglio accedere alle mie risorse con "../resources/file.png"


1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treepuoi fare un esempio di IDE che raccoglie le fonti automaticamente?

7
nessun ide normalmente non raccoglie le fonti automaticamente, perché non è necessario. Quando aggiungo un nuovo file o una nuova cartella, lo faccio all'interno dell'ide e il progetto viene aggiornato. Un sistema di compilazione dall'altra parte non si accorge quando cambio alcuni file, quindi è un comportamento desiderato che raccolga automaticamente tutti i file sorgente
Arne

4
Se vedo quel collegamento, ho l'impressione che CMake abbia fallito nel compito più importante che voleva risolvere: rendere facile un sistema di build multipiattaforma.
Arne

Risposte:


94

dopo alcune ricerche ho ora la mia versione del più semplice ma completo esempio di cmake. Eccolo, e cerca di coprire la maggior parte delle basi, comprese le risorse e il packaging.

una cosa che fa non standard è la gestione delle risorse. Per impostazione predefinita, cmake vuole metterli in / usr / share /, / usr / local / share / e qualcosa di equivalente su Windows. Volevo avere un semplice zip / tar.gz che puoi estrarre ovunque ed eseguire. Pertanto le risorse vengono caricate rispetto all'eseguibile.

la regola di base per comprendere i comandi cmake è la seguente sintassi: <function-name>(<arg1> [<arg2> ...])senza virgola o punto e virgola. Ogni argomento è una stringa. foobar(3.0)ed foobar("3.0")è lo stesso. puoi impostare liste / variabili con set(args arg1 arg2). Con questa variabile impostata foobar(${args}) e foobar(arg1 arg2)sono effettivamente gli stessi. Una variabile inesistente è equivalente a un elenco vuoto. Un elenco è internamente solo una stringa con punto e virgola per separare gli elementi. Quindi una lista con un solo elemento è per definizione solo quell'elemento, non ha luogo alcun inscatolamento. Le variabili sono globali. Le funzioni incorporate offrono una qualche forma di argomenti con nome dal fatto che si aspettano alcuni id come PUBLICoDESTINATIONnella loro lista di argomenti, per raggruppare gli argomenti. Ma questa non è una caratteristica del linguaggio, anche quegli ID sono solo stringhe e analizzati dall'implementazione della funzione.

puoi clonare tutto da GitHub

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)

8
@SteveLorimer Sono solo in disaccordo, quel file globbing è uno stile pessimo, penso che copiare manualmente l'albero dei file in CMakeLists.txt sia uno stile pessimo perché è ridondante. Ma so che le persone non sono d'accordo su questo argomento, quindi ho lasciato un commento nel codice, dove puoi sostituire il globbing con un elenco che contiene tutti i file sorgente esplicitamente. Cerca set(sources src/main.cpp).
Arne

3
@SteveLorimer sì, spesso ho dovuto richiamare di nuovo cmake. Ogni volta che aggiungo qualcosa nell'albero delle directory, devo richiamare manualmente cmake, in modo che il globbing venga rivalutato. Se metti i file in CMakeLists.txt, allora un normale make (o ninja) attiverà la reinvocazione di cmake, quindi non puoi dimenticarlo. È anche un po 'più amichevole per il team, perché anche i membri del team non possono dimenticare di eseguire cmake. Ma penso che un makefile non dovrebbe aver bisogno di essere toccato, solo perché qualcuno ha aggiunto un file. Scrivilo una volta e nessuno dovrebbe pensarci mai più.
Arne

3
@SteveLorimer Sono anche in disaccordo con il modello di mettere un CMakeLists.txt in ogni directory dei progetti, sparge la configurazione del progetto ovunque, penso che un file per fare tutto dovrebbe essere sufficiente, altrimenti perdi la panoramica, di cosa viene effettivamente eseguito nel processo di compilazione. Ciò non significa che non possano esserci sottodirectory con il proprio CMakeLists.txt, penso solo che dovrebbe essere un'eccezione.
Arne

2
Supponendo che "VCS" sia l'abbreviazione di "sistema di controllo della versione" , questo è irrilevante. Il problema non è che gli artefatti non verranno aggiunti al controllo del codice sorgente. Il problema è che CMake non riuscirà a rivalutare i file di origine aggiunti. Non rigenererà i file di input del sistema di compilazione. Il sistema di compilazione rimarrà felicemente con i file di input obsoleti, portando a errori (se sei fortunato), o passando inosservato, se sei sfortunato. GLOBbing produce una lacuna nella catena di calcolo delle dipendenze. Questo è un problema significativo e un commento non lo riconosce in modo appropriato.
Rilevabile l'

2
CMake e un VCS operano in completo isolamento. Il VCS non è a conoscenza di CMake e CMake non è a conoscenza di alcun VCS. Non c'è alcun collegamento tra di loro. A meno che tu non suggerisca agli sviluppatori di eseguire passaggi manuali, estraendo le informazioni dal VCS e sulla base di alcune euristiche pulite e rieseguite CMake. Questo non scala, ovviamente, ed è suscettibile all'errore che è peculiare degli umani. No, mi dispiace, finora non hai fatto un punto convincente per i file GLOBbing.
Rilevabile l'

39

L'esempio più semplice ma completo può essere trovato nel tutorial CMake :

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

Per il tuo esempio di progetto potresti avere:

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)

Per la tua domanda aggiuntiva, un modo per andare è di nuovo nel tutorial: creare un file di intestazione configurabile da includere nel codice. Per questo, crea un file configuration.h.incon i seguenti contenuti:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Quindi nella tua CMakeLists.txtaggiunta:

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}")

Infine, dove hai bisogno del percorso nel tuo codice, puoi fare:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";

grazie mille, soprattutto per RESOURCE_PATH, in qualche modo non ho capito che configure_file è quello che stavo cercando. Ma hai aggiunto manualmente tutti i file dal progetto, esiste un modo migliore per definire semplicemente un modello in cui tutti i file vengono aggiunti dall'albero src?
Arne

Vedi la risposta di Dieter, ma anche i miei commenti sul perché non dovresti usarlo. Se vuoi davvero automatizzarlo, un approccio migliore potrebbe essere quello di scrivere uno script che puoi eseguire per rigenerare l'elenco dei file sorgente (o utilizzare un IDE cmake aware che lo faccia per te; non ne ho familiarità).
sgvd

3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO è una cattiva idea codificare il percorso assoluto della directory di origine. E se hai bisogno di installare il tuo progetto?

2
So che la raccolta automatica delle fonti suona bene, ma può portare a tutti i tipi di complicazioni. Vedi questa domanda di qualche tempo fa per una breve discussione: stackoverflow.com/q/10914607/1401351 .
Peter,

2
Ottieni esattamente lo stesso errore se non esegui cmake; l'aggiunta manuale di file richiede un secondo una volta, l'esecuzione di cmake ad ogni compilazione richiede un secondo ogni volta; effettivamente rompi una caratteristica di cmake; qualcuno che lavora sullo stesso progetto e inserisce le tue modifiche farebbe: esegue make -> ottieni riferimenti non definiti -> si spera ricordati di rieseguire cmake, o file bug con te -> esegue cmake -> esegue make con successo, mentre se aggiungi file a mano lo fa: corre fa con successo -> passa il tempo con la famiglia. Riassumendo, non essere pigro e risparmia a te stesso e agli altri un mal di testa in futuro.
sgvd

2

Qui scrivo un esempio di file CMakeLists.txt molto semplice ma completo.

Codice sorgente

  1. Tutorial da Hello World a multipiattaforma Android / iOS / Web / Desktop.
  2. Ogni piattaforma ho rilasciato un'applicazione di esempio.
  3. La struttura del file 08-cross_platform è stata verificata dal mio lavoro
  4. Potrebbe non essere perfetto ma utile e migliore pratica per un team da solo

Dopodiché, ho offerto un documento per i dettagli.

Se hai domande, puoi contattarmi e vorrei spiegarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.