¿Cómo se usa CMake? [cerrado]

95

Es muy difícil obtener información útil sobre CMake como principiante. Hasta ahora, he visto algunos tutoriales sobre cómo configurar un proyecto muy básico u otro. Sin embargo, ninguno de estos explica el razonamiento detrás de todo lo que se muestra en ellos, dejando siempre muchos huecos que llenar.

¿Qué quiere llamar CMake en un CMakeLists quiere decir ? ¿Se supone que se debe llamar una vez por árbol de compilación o qué? ¿Cómo utilizo configuraciones diferentes para cada compilación si todas usan el mismo archivo CMakeLists.txt de la misma fuente? ¿Por qué cada subdirectorio necesita su propio archivo CMakeLists? ¿Tendría sentido usar CMake en un CMakeLists.txt que no sea el que está en la raíz del proyecto? Si es así, ¿en qué casos? ¿Cuál es la diferencia entre especificar cómo construir un ejecutable o biblioteca desde el archivo CMakeLists en su propio subdirectorio versus hacerlo en el archivo CMakeLists en la raíz de todas las fuentes? ¿Puedo hacer un proyecto para Eclipse y otro para Visual Studio, simplemente cambiando la -Gopción al llamar a CMake? ¿Es así como se usa?

Ninguno de los tutoriales, páginas de documentación o preguntas / respuestas que he visto hasta ahora brindan información útil para comprender cómo usar CMake. Los ejemplos simplemente no son completos. No importa qué tutoriales lea, siento que me estoy perdiendo algo importante.

Hay muchas preguntas hechas por novatos de CMake como yo que no hacen esto explícitamente, pero que hacen evidente el hecho de que, como novatos, no tenemos idea de cómo lidiar con CMake o qué hacer con él.

leinaD_natipaC
fuente
8
Quizás deberías escribir una publicación de blog en su lugar.
steveire
2
Estoy tentado a cerrar el voto como "demasiado amplio" ... Esto se siente más como un ensayo personal sobre CMake que como un buen ajuste para stackoverflow. ¿Por qué no puso todas estas preguntas en preguntas separadas? Y cómo es su pregunta + respuesta diferente de muchos otros, por ejemplo stackoverflow.com/q/20884380/417197 , stackoverflow.com/q/4745996/417197 , stackoverflow.com/q/4506193/417197 ?
André
13
@Andre ¿Por qué esta pregunta? Pasé una semana tratando de entender CMake y ningún tutorial que encontré estaba completo. Esos enlaces que señaló son buenos, pero para alguien que encontró CMake porque le lanzaron un proyecto con un montón de CMakeLists adentro, no arrojan mucha luz sobre cómo funciona CMake. Honestamente, intenté escribir una respuesta que desearía poder enviarme en el tiempo cuando comencé a tratar de aprender CMake. Y las subpreguntas están destinadas a mostrar que en realidad estoy tratando de preguntar qué es lo que debo saber para no tener esas dudas. Hacer la pregunta correcta es DIFÍCIL.
leinaD_natipaC
2
No es una respuesta, pero te invito a que eches un vistazo a jaws.rootdirectory.de . Mientras que la documentación de CMake (y la respuesta aceptada ...) le muestra muchos fragmentos de lo que podría hacer, escribí una configuración básica (de ninguna manera "completa" o "perfecta"), que incluye comentarios en todo momento, que En mi humilde opinión, muestra lo que CMake (y CTest y CPack y CDash ...) pueden hacer por usted. Se construye listo para usar y puede adaptarlo / ampliarlo fácilmente a las necesidades de su proyecto.
DevSolar
9
Es una pena que estas cuestiones se cierren hoy en día. Creo que las preguntas amplias que requieren respuestas como tutoriales, pero que tratan sobre temas pobremente / poco documentados (otro ejemplo sería la API de Torch7 C) y las preguntas de nivel de investigación deben permanecer abiertas. Esos son mucho más interesantes que el típico "hey, tengo un error de segmentación al hacer algún proyecto de juguete" que plaga el sitio.
Ash

Respuestas:

137

¿Para qué sirve CMake?

Según Wikipedia:

CMake es un software para [...] administrar el proceso de construcción de software utilizando un método independiente del compilador. Está diseñado para admitir jerarquías de directorios y aplicaciones que dependen de varias bibliotecas. Se utiliza junto con entornos de compilación nativos como make, Xcode de Apple y Microsoft Visual Studio.

Con CMake, ya no necesita mantener configuraciones separadas específicas para su compilador / entorno de compilación. Tiene una configuración y funciona para muchos entornos.

CMake puede generar una solución de Microsoft Visual Studio, un proyecto de Eclipse o un laberinto de Makefile a partir de los mismos archivos sin cambiar nada en ellos.

Dado un montón de directorios con código en ellos, CMake administra todas las dependencias, órdenes de compilación y otras tareas que su proyecto necesita para ser compilado. En realidad, NO compila nada. Para usar CMake, debe decirle (usando archivos de configuración llamados CMakeLists.txt) qué ejecutables necesita compilar, a qué bibliotecas se vinculan, qué directorios hay en su proyecto y qué hay dentro de ellos, así como cualquier detalle como banderas o cualquier otra cosa que necesite (CMake es bastante poderoso).

Si está configurado correctamente, utilice CMake para crear todos los archivos que su "entorno de compilación nativo" de elección necesita para hacer su trabajo. En Linux, por defecto, esto significa Makefiles. Entonces, una vez que ejecute CMake, creará un montón de archivos para su propio uso más algunos Makefiles. Todo lo que necesita hacer a partir de entonces es escribir "make" en la consola desde la carpeta raíz cada vez que termine de editar su código, y bam, se crea un ejecutable compilado y vinculado.

¿Cómo actúa CMake? ¿Qué hace?

Aquí hay una configuración de proyecto de ejemplo que usaré en todo momento:

simple/
  CMakeLists.txt
  src/
    tutorial.cxx
    CMakeLists.txt
  lib/
    TestLib.cxx
    TestLib.h
    CMakeLists.txt
  build/

El contenido de cada archivo se muestra y comenta más adelante.

CMake configura su proyecto de acuerdo con la raíz CMakeLists.txt de su proyecto, y lo hace en cualquier directorio que haya ejecutado cmakeen la consola. Hacer esto desde una carpeta que no es la raíz de su proyecto produce lo que se llama una compilación fuera de origen , lo que significa que los archivos creados durante la compilación (archivos obj, archivos lib, ejecutables, ya sabe) se colocarán en dicha carpeta , mantenido separado del código real. Ayuda a reducir el desorden y también se prefiere por otras razones, que no discutiré.

No sé qué sucede si ejecuta cmakeen cualquier otro que no sea la raíz CMakeLists.txt.

En este ejemplo, como quiero que todo esté dentro de la build/carpeta, primero tengo que navegar allí y luego pasar CMake el directorio en el que CMakeLists.txtreside la raíz .

cd build
cmake ..

Por defecto, esto configura todo usando Makefiles como he dicho. Así es como debería verse la carpeta de compilación ahora:

simple/build/
  CMakeCache.txt
  cmake_install.cmake
  Makefile
  CMakeFiles/
    (...)
  src/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile
  lib/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile

¿Qué son todos estos archivos? Lo único de lo que debe preocuparse es el Makefile y las carpetas del proyecto .

Observe las carpetas src/y lib/. Estos se han creado porque simple/CMakeLists.txtapunta a ellos usando el comando add_subdirectory(<folder>). Este comando le dice a CMake que busque en dicha carpeta otro CMakeLists.txtarchivo y ejecute ese script, por lo que cada subdirectorio agregado de esta manera debe tener un CMakeLists.txtarchivo dentro. En este proyecto, simple/src/CMakeLists.txtdescribe cómo construir el ejecutable real y simple/lib/CMakeLists.txtdescribe cómo construir la biblioteca. Cada objetivo que CMakeLists.txtdescribe se colocará de forma predeterminada en su subdirectorio dentro del árbol de compilación. Entonces, después de un rápido

make

en la consola hecha desde build/, se agregan algunos archivos:

simple/build/
  (...)
  lib/
    libTestLib.a
    (...)
  src/
    Tutorial
    (...)

El proyecto está construido y el ejecutable está listo para ejecutarse. ¿Qué haces si quieres que los ejecutables se coloquen en una carpeta específica? Establezca la variable CMake adecuada o cambie las propiedades de un objetivo específico . Más sobre las variables de CMake más adelante.

¿Cómo le digo a CMake cómo construir mi proyecto?

Aquí está el contenido, explicado, de cada archivo en el directorio fuente:

simple/CMakeLists.txt:

cmake_minimum_required(VERSION 2.6)

project(Tutorial)

# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)

La versión mínima requerida siempre debe establecerse, de acuerdo con la advertencia que CMake lanza cuando no lo hace. Utilice cualquiera que sea su versión de CMake.

El nombre de su proyecto se puede usar más adelante y sugiere el hecho de que puede administrar más de un proyecto desde los mismos archivos de CMake. Sin embargo, no profundizaré en eso.

Como se mencionó anteriormente, add_subdirectory()agrega una carpeta al proyecto, lo que significa que CMake espera que tenga un contenido CMakeLists.txtinterno, que luego se ejecutará antes de continuar. Por cierto, si tiene una función CMake definida, puede usarla desde otros CMakeLists.txts en subdirectorios, pero debe definirla antes de usarla add_subdirectory()o no la encontrará. Sin embargo, CMake es más inteligente con las bibliotecas, por lo que es probable que esta sea la única vez que se encuentre con este tipo de problema.

simple/lib/CMakeLists.txt:

add_library(TestLib TestLib.cxx)

Para crear su propia biblioteca, le da un nombre y luego enumera todos los archivos a partir de los cuales se construyó. Sencillo. Si necesitara otro archivo, foo.cxxpara ser compilado, en su lugar escribiría add_library(TestLib TestLib.cxx foo.cxx). Esto también funciona para archivos en otros directorios, por ejemplo add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx). Más sobre la variable CMAKE_SOURCE_DIR más adelante.

Otra cosa que puede hacer con esto es especificar que desea una biblioteca compartida. El ejemplo: add_library(TestLib SHARED TestLib.cxx). No temas, aquí es donde CMake comienza a hacer tu vida más fácil. Ya sea compartido o no, ahora todo lo que necesita manejar para usar una biblioteca creada de esta manera es el nombre que le dio aquí. El nombre de esta biblioteca ahora es TestLib y puede hacer referencia a ella desde cualquier lugar del proyecto. CMake lo encontrará.

¿Existe una mejor manera de enumerar las dependencias? Definitivamente si . Consulte a continuación para obtener más información sobre esto.

simple/lib/TestLib.cxx:

#include <stdio.h>

void test() {
  printf("testing...\n");
}

simple/lib/TestLib.h:

#ifndef TestLib
#define TestLib

void test();

#endif

simple/src/CMakeLists.txt:

# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)

# Link to needed libraries
target_link_libraries(Tutorial TestLib)

# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)

El comando add_executable()funciona exactamente igual que add_library(), excepto, por supuesto, que generará un ejecutable. Ahora se puede hacer referencia a este ejecutable como destino para cosas como target_link_libraries(). Dado que tutorial.cxx usa el código que se encuentra en la biblioteca TestLib, señale esto a CMake como se muestra.

De manera similar, cualquier archivo .h # incluido por cualquier fuente add_executable()que no esté en el mismo directorio que la fuente debe agregarse de alguna manera. Si no fuera por el target_include_directories()comando, lib/TestLib.hno se encontraría al compilar el Tutorial, por lo que la lib/carpeta completa se agrega a los directorios de inclusión para buscar #includes. También puede ver el comando include_directories()que actúa de manera similar, excepto que no necesita que especifique un objetivo, ya que lo establece de forma global, para todos los ejecutables. Una vez más, explicaré CMAKE_SOURCE_DIR más adelante.

simple/src/tutorial.cxx:

#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
  test();
  fprintf(stdout, "Main\n");
  return 0;
}

Observe cómo se incluye el archivo "TestLib.h". No es necesario incluir la ruta completa: CMake se encarga de todo eso detrás de escena gracias a target_include_directories().

Técnicamente hablando, en un árbol de código fuente simple como esto se puede hacer sin los CMakeLists.txts bajo lib/y src/agregando solo algo parecido add_executable(Tutorial src/tutorial.cxx)a simple/CMakeLists.txt. Depende de usted y de las necesidades de su proyecto.

¿Qué más debo saber para usar CMake correctamente?

(AKA temas relevantes para su comprensión)

Encontrar y usar paquetes : la respuesta a esta pregunta lo explica mejor que nunca.

Declaración de variables y funciones, uso de flujo de control, etc .: consulte este tutorial que explica los conceptos básicos de lo que CMake tiene para ofrecer, además de ser una buena introducción en general.

Variables de CMake : hay muchas, así que lo que sigue es un curso intensivo para llevarlo por el camino correcto. La wiki de CMake es un buen lugar para obtener información más detallada sobre variables y, aparentemente, también sobre otras cosas.

Es posible que desee editar algunas variables sin reconstruir el árbol de compilación. Utilice ccmake para esto (edita el CMakeCache.txtarchivo). Recuerde configurar ccuando haya terminado con los cambios y luego ggenerar archivos MAKE con la configuración actualizada.

Lea el tutorial al que se hizo referencia anteriormente para aprender a usar variables, pero en pocas palabras: set(<variable name> value)para cambiar o crear una variable. ${<variable name>}para usarlo.

  • CMAKE_SOURCE_DIR: El directorio raíz de la fuente. En el ejemplo anterior, esto siempre es igual a/simple
  • CMAKE_BINARY_DIR: El directorio raíz de la compilación. En el ejemplo anterior, esto es igual a simple/build/, pero si ejecutó cmake simple/desde una carpeta como foo/bar/etc/, todas las referencias a CMAKE_BINARY_DIRen ese árbol de compilación se convertirían en /foo/bar/etc.
  • CMAKE_CURRENT_SOURCE_DIR: El directorio en el que se encuentra la corriente CMakeLists.txt. Esto significa que cambia completamente: imprimiendo esto desde simple/CMakeLists.txtrendimientos /simplee imprimiéndolo desde simple/src/CMakeLists.txtrendimientos /simple/src.
  • CMAKE_CURRENT_BINARY_DIR: Entiendes la idea. Esta ruta dependería no solo de la carpeta en la que se encuentra la compilación, sino también de la CMakeLists.txtubicación del script actual .

¿Por qué son estos importantes? Los archivos fuente, obviamente, no estarán en el árbol de construcción. Si intentas algo como target_include_directories(Tutorial PUBLIC ../lib)en el ejemplo anterior, esa ruta será relativa al árbol de construcción, es decir, será como escribir ${CMAKE_BINARY_DIR}/lib, que mirará dentro simple/build/lib/. No hay archivos .h ahí; a lo sumo encontrarás libTestLib.a. Tu quieres en su ${CMAKE_SOURCE_DIR}/liblugar.

  • CMAKE_CXX_FLAGS: Indicadores para pasar al compilador, en este caso el compilador de C ++. También vale la pena señalar CMAKE_CXX_FLAGS_DEBUGcuál se usará en su lugar si CMAKE_BUILD_TYPEse establece en DEBUG. Hay más como estos; echa un vistazo a la wiki de CMake .
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY: Dile a CMake dónde poner todos los ejecutables cuando se compile. Este es un escenario global. Puede, por ejemplo, configurarlo bin/y tener todo perfectamente colocado allí. EXECUTABLE_OUTPUT_PATHes similar, pero desaprobado, en caso de que lo encuentre.
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY: Del mismo modo, una configuración global para decirle a CMake dónde colocar todos los archivos de la biblioteca.

Propiedades de destino : puede establecer propiedades que afecten solo a un destino, ya sea un ejecutable o una biblioteca (o un archivo ... se hace una idea). Aquí hay un buen ejemplo de cómo usarlo (con set_target_properties().

¿Existe una manera fácil de agregar fuentes a un destino automáticamente? Utilice GLOB para enumerar todo en un directorio determinado bajo la misma variable. La sintaxis de ejemplo es FILE(GLOB <variable name> <directory>/*.cxx).

¿Puede especificar diferentes tipos de compilación? Sí, aunque no estoy seguro de cómo funciona esto o las limitaciones de esto. Probablemente requiera algo de if / then'ning, pero CMake ofrece algún soporte básico sin configurar nada, como los valores predeterminados para CMAKE_CXX_FLAGS_DEBUG, por ejemplo. Puede establecer su tipo de compilación desde dentro del CMakeLists.txtarchivo a través set(CMAKE_BUILD_TYPE <type>)o llamando a CMake desde la consola con los indicadores apropiados, por ejemplo cmake -DCMAKE_BUILD_TYPE=Debug.

¿Algún buen ejemplo de proyectos que utilizan CMake? Wikipedia tiene una lista de proyectos de código abierto que usan CMake, si quieres investigarlo. Los tutoriales en línea no han sido más que una decepción para mí hasta ahora en este sentido, sin embargo, esta pregunta de Stack Overflow tiene una configuración de CMake bastante interesante y fácil de entender. Vale la pena echarle un vistazo.

Usando variables de CMake en su código : Aquí hay un ejemplo rápido y sucio (adaptado de algún otro tutorial ):

simple/CMakeLists.txt:

project (Tutorial)

# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)

# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)

simple/TutorialConfig.h.in:

// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

El archivo resultante generada por CMake, simple/src/TutorialConfig.h:

// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1

Con un uso inteligente de estos, puede hacer cosas interesantes como apagar una biblioteca y demás. Recomiendo echar un vistazo a ese tutorial, ya que hay algunas cosas un poco más avanzadas que seguramente serán muy útiles en proyectos más grandes, tarde o temprano.

Para todo lo demás, Stack Overflow está repleto de preguntas específicas y respuestas concisas, lo cual es excelente para todos, excepto para los no iniciados.

leinaD_natipaC
fuente
2
Acepto esta respuesta, pero si alguien más escribe una respuesta mejor y más corta, la elegiré. Lo haría yo mismo, pero ya he sufrido lo suficiente por CMake.
leinaD_natipaC
6
Gracias por este increíble trabajo. ¡Espero que esto obtenga más votos! :)
LET
4
El punto más bien subestimado allí: con CMake, ya no necesita mantener configuraciones separadas específicas para su compilador / entorno de compilación. Tiene una configuración que funciona para (varias versiones de) Visual Studio, Code Blocks, archivos Makefiles simples de Unix y muchos otros entornos. Por eso estaba mirando CMake en primer lugar: se convirtió en un gran problema mantener sincronizadas las configuraciones Autoconf, MSVC 2005 / 32bit y MSVC 2010 / 64bit. Y una vez que le puse el truco, agregué muchas más cosas como generación de documentos, pruebas, empaquetado, etc., todo de una manera multiplataforma.
DevSolar
1
@DevSolar True. Me gusta más esa línea que la de Wikipedia, así que la agregaré a la respuesta si no le importa. Sin embargo, no encajaré en nada sobre cómo usar CMake correctamente para ese efecto. Se trata más de saber qué está pasando.
leinaD_natipaC
1
@RichieHH Siguiendo el ejemplo, primero configura CMake para usar Makefiles, luego usa CMake para generar los archivos que su proyecto necesita para ser construido, y finalmente deja de preocuparse por CMake porque su trabajo aquí está hecho. Así que te "preocupas" por el Makefile en el sentido de que a partir de ahora necesitas usar makeen bash para, bueno, hacer tu proyecto.
leinaD_natipaC