Ejemplo de CMake más simple pero completo

117

De alguna manera, estoy totalmente confundido por cómo funciona CMake. Cada vez que pienso que me estoy acercando a entender cómo se debe escribir CMake, desaparece en el siguiente ejemplo que leo. Todo lo que quiero saber es cómo debería estructurar mi proyecto para que mi CMake requiera la menor cantidad de mantenimiento en el futuro. Por ejemplo, no quiero actualizar mi CMakeList.txt cuando agrego una nueva carpeta en mi árbol src, que funciona exactamente como todas las demás carpetas src.

Así es como imagino la estructura de mi proyecto, pero por favor, esto es solo un ejemplo. Si la forma recomendada difiere, por favor dígame y dígame cómo hacerlo.

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

Por cierto, es importante que mi programa sepa dónde están los recursos. Me gustaría conocer la forma recomendada de gestionar los recursos. No quiero acceder a mis recursos con "../resources/file.png"

Arne
fuente
1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src tree¿Puede dar un ejemplo de IDE que recopila fuentes automáticamente?
7
no ide normalmente no recopila fuentes automáticamente, porque no es necesario. Cuando agrego un nuevo archivo o carpeta, lo hago dentro del ide y el proyecto se actualiza. Un sistema de compilación en el otro lado no se da cuenta cuando cambio algunos archivos, por lo que es un comportamiento deseado que recopile todos los archivos fuente automáticamente
Arne
4
Si veo ese enlace, tengo la impresión de que CMake falló en la tarea más importante que quería resolver: facilitar un sistema de compilación multiplataforma.
Arne

Respuestas:

94

Después de algunas investigaciones, ahora tengo mi propia versión del ejemplo de cmake más simple pero completo. Aquí está e intenta cubrir la mayoría de los conceptos básicos, incluidos los recursos y el empaque.

una cosa que hace de manera no estándar es el manejo de recursos. Por defecto, cmake quiere ponerlos en / usr / share /, / usr / local / share / y algo equivalente en Windows. Quería tener un zip / tar.gz simple que puedas extraer en cualquier lugar y ejecutar. Por lo tanto, los recursos se cargan en relación con el ejecutable.

la regla básica para entender los comandos de cmake es la siguiente sintaxis: <function-name>(<arg1> [<arg2> ...])sin coma o semicolor. Cada argumento es una cadena. foobar(3.0)y foobar("3.0")es lo mismo. puede configurar listas / variables con set(args arg1 arg2). Con este conjunto de variables foobar(${args}) y foobar(arg1 arg2)son efectivamente iguales. Una variable inexistente equivale a una lista vacía. Una lista es internamente solo una cadena con punto y coma para separar los elementos. Por lo tanto, una lista con un solo elemento es, por definición, solo ese elemento, no tiene lugar ningún boxeo. Las variables son globales. Las funciones integradas ofrecen alguna forma de argumentos con nombre por el hecho de que esperan algunos identificadores como PUBLICoDESTINATIONen su lista de argumentos, para agrupar los argumentos. Pero esa no es una característica del lenguaje, esos identificadores también son solo cadenas y se analizan mediante la implementación de la función.

puedes clonar todo desde 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)
Arne
fuente
8
@SteveLorimer No estoy de acuerdo, que el archivo global es un mal estilo, creo que copiar manualmente el árbol de archivos en CMakeLists.txt es un mal estilo porque es redundante. Pero sé que la gente no está de acuerdo con este tema, por lo tanto, dejé un comentario en el código, donde puede reemplazar el globbing con una lista que contiene todos los archivos fuente explícitamente. Buscar set(sources src/main.cpp).
Arne
3
@SteveLorimer sí, a menudo tuve que invocar cmake nuevamente. Cada vez que agrego algo en el árbol de directorios, necesito volver a invocar cmake manualmente, para que se reevalúe el globbing. Si pones los archivos en el CMakeLists.txt, entonces un make normal (o ninja) desencadenará la reinvocación de cmake, por lo que no puedes olvidarlo. También es un poco más amigable para el equipo, porque los miembros del equipo tampoco pueden olvidarse de ejecutar cmake. Pero creo que un archivo MAKE no debería necesitar ser tocado, solo porque alguien agregó un archivo. Escríbalo una vez, y nadie debería tener que pensar en ello nunca más.
Arne
3
@SteveLorimer También estoy en desacuerdo con el patrón para poner un CMakeLists.txt en cada directorio de los proyectos, solo dispersa la configuración del proyecto en todas partes, creo que un archivo para hacerlo todo debería ser suficiente, de lo contrario, perderá la descripción general de qué se hace realmente en el proceso de construcción. Eso no significa que no pueda haber subdirectorios con su propio CMakeLists.txt, solo creo que debería ser una excepción.
Arne
2
Suponiendo que "VCS" es la abreviatura de "sistema de control de versiones" , entonces eso es irrelevante. El problema no es que los artefactos no se agregarán al control de fuente. El problema es que CMake no volverá a evaluar los archivos fuente agregados. No volverá a generar archivos de entrada del sistema de compilación. El sistema de compilación se quedará felizmente con los archivos de entrada obsoletos, ya sea dando lugar a errores (si tiene suerte), o pasando desapercibido, si se queda sin suerte. GLOBbing produce una brecha en la cadena de cálculo de dependencia. Este es un problema importante y un comentario no reconoce esto de manera adecuada.
Inspectable
2
CMake y un VCS funcionan en completo aislamiento. El VCS desconoce CMake y CMake desconoce ningún VCS. No existe ningún vínculo entre ellos. A menos que sugiera que los desarrolladores deben tomar pasos manuales, sacar información del VCS y basarse en alguna limpieza heurística y volver a ejecutar CMake. Eso no escala, obviamente, y es susceptible a la falacia que es peculiar de los humanos. No, lo siento, no ha hecho un punto convincente para los archivos GLOBbing hasta ahora.
IInspectable
39

El ejemplo más básico pero completo se puede encontrar en el tutorial de CMake :

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

Para su ejemplo de proyecto, puede tener:

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)

Para su pregunta adicional, una forma de hacerlo es nuevamente en el tutorial: cree un archivo de encabezado configurable que incluya en su código. Para ello, haga un archivo configuration.h.incon el siguiente contenido:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Luego, en tu CMakeLists.txtcomplemento:

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

Finalmente, donde necesite la ruta en su código, puede hacer:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";
sgvd
fuente
muchas gracias, especialmente por RESOURCE_PATH, de alguna manera no entendí que configure_file es lo que estaba buscando. Pero agregó todos los archivos del proyecto manualmente, ¿hay una mejor manera de simplemente definir un patrón en el que todos los archivos se agregan desde el árbol src?
Arne
Vea la respuesta de Dieter, pero también mis comentarios sobre por qué no debería usarlo. Si realmente desea automatizarlo, un mejor enfoque puede ser escribir un script que pueda ejecutar para regenerar la lista de archivos fuente (o usar un IDE compatible con cmake que haga esto por usted; no estoy familiarizado con ninguno).
sgvd
3
@sgvd En string resourcePath = string(RESOURCE_PATH) + "file.png"mi humilde opinión, es una mala idea codificar la ruta absoluta al directorio de origen. ¿Qué pasa si necesita instalar su proyecto?
2
Sé que la recopilación automática de fuentes suena bien, pero puede dar lugar a todo tipo de complicaciones. Vea esta pregunta de hace un tiempo para una breve discusión: stackoverflow.com/q/10914607/1401351 .
Peter
2
Obtiene exactamente el mismo error si no ejecuta cmake; agregar archivos manualmente toma un segundo una vez, ejecutar cmake en cada compilación toma un segundo cada vez; realmente rompe una característica de cmake; alguien que trabaje en el mismo proyecto y extraiga sus cambios haría: ejecuta make -> obtener referencias indefinidas -> con suerte, recuerde volver a ejecutar cmake, o file bug con usted -> ejecuta cmake -> ejecuta make con éxito, mientras que si agrega el archivo a mano lo hace: corre hacer con éxito -> pasa tiempo con la familia. Resuma eso, no sea perezoso, y ahórrese a usted y a los demás un dolor de cabeza en el futuro.
sgvd
2

Aquí escribo una muestra de archivos CMakeLists.txt más simple pero completa.

Código fuente

  1. Tutoriales desde hello world hasta multiplataforma Android / iOS / Web / Desktop.
  2. Cada plataforma lancé una aplicación de muestra.
  3. Mi trabajo verifica la estructura del archivo 08-cross_platform
  4. Puede que no sea perfecto, pero útil y la mejor práctica para un equipo por mi cuenta

Después de eso, ofrecí un documento para los detalles.

Si tiene alguna pregunta, puede contactarme y me gustaría explicárselo.

MinamiTouma
fuente