¿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 Makefile
s. 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 cmake
en 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 cmake
en 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.txt
reside 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.txt
apunta a ellos usando el comando add_subdirectory(<folder>)
. Este comando le dice a CMake que busque en dicha carpeta otro CMakeLists.txt
archivo y ejecute ese script, por lo que cada subdirectorio agregado de esta manera debe tener un CMakeLists.txt
archivo dentro. En este proyecto, simple/src/CMakeLists.txt
describe cómo construir el ejecutable real y simple/lib/CMakeLists.txt
describe cómo construir la biblioteca. Cada objetivo que CMakeLists.txt
describe 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.txt
interno, que luego se ejecutará antes de continuar. Por cierto, si tiene una función CMake definida, puede usarla desde otros CMakeLists.txt
s 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.cxx
para 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.h
no 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.txt
s 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.txt
archivo). Recuerde configurar c
cuando haya terminado con los cambios y luego g
generar 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_DIR
en 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.txt
rendimientos /simple
e imprimiéndolo desde simple/src/CMakeLists.txt
rendimientos /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.txt
ubicació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}/lib
lugar.
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_DEBUG
cuál se usará en su lugar si CMAKE_BUILD_TYPE
se 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_PATH
es 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.txt
archivo 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.