CMake se refiere a cross-platform make, su uso principal es el de controlar el proceso de compilación de software sin depender de la plataforma. Actualmente abarca múltiples herramientas para administrar la compilación, pruebas, y empaquetado de software. Con CMake es posible generar archivos de compilación Make (Unix), Xcode (Apple), Visual Studio (Microsoft), Ninja, y Qt, entre otros, para proyectos C y C++, también se puede compilar, lanzar la ejecución de pruebas unitarias (ctest), y generar paquetes (cpack).

Conforme han ido evolucionando los estándares para C y C++ también lo ha hecho CMake, facilitando la forma de crear un proyecto. Desde 2014 con la liberación de la versión 3.0 se considera como CMake moderno, en el cual se prioriza el uso de targets y properties sobre las tradicionales variables.

Hola CMake

El primer paso para crear un proyecto CMake C++ es crear el archivo CMakeLists.txt con las instrucciones mínimas requeridas:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.0...3.15)
project(hola_cmake VERSION 0.0.1 LANGUAGES CXX)

add_executable(${PROJECT_NAME} main.cpp)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)

La primera línea es un comentario.
La segunda línea valida que el proyecto sea compatible con la versión de CMake instalada, donde la versión mínima es la 3.0 y la máxima es la 3.15.
La tercera línea define el nombre del proyecto, su versión, y lenguaje.
La siguiente define el Binario Objetivo (target) y los archivos a incluir en éste.
La última línea define el estándar de C++ que el proyecto va a usar.

Con esas instrucciones ya se tienen los mínimos requerimientos para el proyecto hola_cmake con el siguiente código fuente:

// main.cpp
#include <iostream>

int main()
{
  std::cout << "Hola CMake\n";

  return 0;
}

Binarios objetivo: Targets

Un proyecto CMake puede tener el objetivo de crear uno o varios Ejecutables, y Bibliotecas. Los archivos binarios generados tienen diferentes sufijos o prefijos dependiendo de la plataforma destino. Por ejemplo, para los ejecutables en Windows CMake agrega el sufijo .exe. Para las bibliotecas se puede configurar que sea estática (.lib ), o dinámica (compartida .dll, .so, .dylib).

El nombre del target debe ser único en el proyecto, pero pueden existir varios targets con diferente nombre en un mismo proyecto. Desde la versión 3.1 de CMake está disponible el comando target_sources().
En las versiones más recientes (>3.15) los archivos de código fuente se pueden definir por separado de los targets por medio del comando target_sources():

# CMakeLists.txt
#...
add_executable("hola_cmake")
add_library("hola_cmake_bib_estatica"   STATIC)
add_library("hola_cmake_bib_compartida" SHARED)

target_sources("hola_cmake"                PUBLIC main.cpp)
target_sources("hola_cmake_bib_estatica"   PUBLIC foo.cpp)
target_sources("hola_cmake_bib_compartida" PUBLIC bar.cpp)
#...

Opciones

Las opciones son variables personalizadas para el proyecto que el usuario puede modificar sin necesidad de cambiar el archivo CMakeLists.txt:

# CMakeLists.txt
#...
# Options
option(ENABLE_TESTS "Habilitar compilación de pruebas" False)
option(ENABLE_CONAN "Habilitar uso de Conan" False)
#...

Así el usuario puede ejecutar los comandos de generación con valores diferentes dependiendo de la configuración requerida. Por ejemplo para una configuración en modo release se puede dejar sin habilitar la compilación de pruebas, pero para una configuración en modo debug habilitarlas:

cmake .. -DENABLE_TESTS=True

Subdirectorios

Por medio del comando add_subdirectory() es posible indicarle al proyecto que en un subdirectorio hay otro archivo CMakeLists.txt que también hace parte del proyecto. Teniendo un directorio exclusivo para pruebas (test) con su propio archivo de CMakeLists, el archivo principal lo agrega al proyecto así:

# CMakeLists.txt
#...
if(ENABLE_TESTS)
  add_subdirectory(test)
else()
  message("Compilación de pruebas deshabilitada")
endif()

Ejecución de CMake

La ejecución de los comandos CMake se divide en dos pasos: generación y compilación. CMake incluye integración con múltiples IDEs en las cuales se pueden personalizar estos dos pasos.

La generación corresponde a crear los archivos con las instrucciones de compilación de acuerdo al generador seleccionado. Si no se selecciona uno, CMake elige el predeterminado para la plataforma en la cual se está ejecutando. Es recomendable que la generación y compilación se hagan en un directorio diferente al directorio raíz del proyecto, y dentro de éste, pero que sea excluido del sistema de control de versiones.

Para un sistema GNU/Linux con Ubuntu, para el proyecto hola_cmake se puede crear el directorio cmake-build/ desde el cual se ejecuta el comando de generación:

cmake .. -G "Unix Makefiles" -DENABLE_TESTS=True

Donde "Unix Makefiles" es el generador elegido.

La compilación desde CMake crea los archivos binarios para los targets definidos en el proyecto. Desde un terminal Unix el comando de compilación desde el directorio cmake-build/ es:

cmake --build ./

Si los archivos de compilación fueron generados de manera satisfactoria, y la compilación fue exitosa, se habrán creado los objetivos:

hola_cmake 
libhola_cmake_bib_estatica.a
libhola_cmake_bib_compartida.so

Al correr el ejecutable se obtiene el resultado esperado:

$ ./hola_cmake 
Hola CMake

Consideraciones

  • Para versiones de cmake menores a 3.15 pueden aparecer mensajes de advertencia relacionados con el comando target_sources().

Fuentes

Deja un comentario