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 -G
opció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.
Respuestas:
¿Para qué sirve CMake?
Según Wikipedia:
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:
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 ejecutadocmake
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ízCMakeLists.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 queCMakeLists.txt
reside la raíz .Por defecto, esto configura todo usando Makefiles como he dicho. Así es como debería verse la carpeta de compilación ahora:
¿Qué son todos estos archivos? Lo único de lo que debe preocuparse es el Makefile y las carpetas del proyecto .
Observe las carpetas
src/
ylib/
. Estos se han creado porquesimple/CMakeLists.txt
apunta a ellos usando el comandoadd_subdirectory(<folder>)
. Este comando le dice a CMake que busque en dicha carpeta otroCMakeLists.txt
archivo y ejecute ese script, por lo que cada subdirectorio agregado de esta manera debe tener unCMakeLists.txt
archivo dentro. En este proyecto,simple/src/CMakeLists.txt
describe cómo construir el ejecutable real ysimple/lib/CMakeLists.txt
describe cómo construir la biblioteca. Cada objetivo queCMakeLists.txt
describe se colocará de forma predeterminada en su subdirectorio dentro del árbol de compilación. Entonces, después de un rápidoen la consola hecha desde
build/
, se agregan algunos archivos: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
: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 contenidoCMakeLists.txt
interno, que luego se ejecutará antes de continuar. Por cierto, si tiene una función CMake definida, puede usarla desde otrosCMakeLists.txt
s en subdirectorios, pero debe definirla antes de usarlaadd_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
: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íaadd_library(TestLib TestLib.cxx foo.cxx)
. Esto también funciona para archivos en otros directorios, por ejemploadd_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
:simple/lib/TestLib.h
:simple/src/CMakeLists.txt
:El comando
add_executable()
funciona exactamente igual queadd_library()
, excepto, por supuesto, que generará un ejecutable. Ahora se puede hacer referencia a este ejecutable como destino para cosas comotarget_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 eltarget_include_directories()
comando,lib/TestLib.h
no se encontraría al compilar el Tutorial, por lo que lalib/
carpeta completa se agrega a los directorios de inclusión para buscar #includes. También puede ver el comandoinclude_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
: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 bajolib/
ysrc/
agregando solo algo parecidoadd_executable(Tutorial src/tutorial.cxx)
asimple/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 configurarc
cuando haya terminado con los cambios y luegog
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 asimple/build/
, pero si ejecutócmake simple/
desde una carpeta comofoo/bar/etc/
, todas las referencias aCMAKE_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 corrienteCMakeLists.txt
. Esto significa que cambia completamente: imprimiendo esto desdesimple/CMakeLists.txt
rendimientos/simple
e imprimiéndolo desdesimple/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 laCMakeLists.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á dentrosimple/build/lib/
. No hay archivos .h ahí; a lo sumo encontraráslibTestLib.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ñalarCMAKE_CXX_FLAGS_DEBUG
cuál se usará en su lugar siCMAKE_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, configurarlobin/
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 delCMakeLists.txt
archivo a travésset(CMAKE_BUILD_TYPE <type>)
o llamando a CMake desde la consola con los indicadores apropiados, por ejemplocmake -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
:simple/TutorialConfig.h.in
:El archivo resultante generada por CMake,
simple/src/TutorialConfig.h
: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.
fuente
make
en bash para, bueno, hacer tu proyecto.