Integrando VSCode, Mingw-w64 y CMake (Parte V)

Lecturas de la serie
Sobre este artículo
     Tiempo estimado de lectura: 15 minutos
     Nivel: Intermedio
     Última actualización: 3 de agosto, 2026

1.  Introducción

En los artículos anteriores de esta serie hemos empleado CMake para construir aplicaciones sencillas compuestas por un único fichero de código fuente (.cpp) y algunas dependencias externas (bibliotecas de terceros). Sin embargo, los proyectos reales suelen organizarse en varios módulos o bibliotecas internas, cada uno con sus propios ficheros de cabecera, archivos de implementación y ficheros CMakeLists.txt de configuración.

En este artículo analizaremos una estructura habitual que separa el código en bibliotecas reutilizables y una aplicación ejecutable que las consume. Veremos cómo organizar el árbol de directorios, cómo definir los distintos targets mediante ficheros CMakeLists.txt independientes y cómo expresar las dependencias entre ellos.

Nuestro árbol de directorios tomará la forma siguiente:

sample_project
├── CMakeLists.txt
├── lib_1
│   ├── CMakeLists.txt
│   ├── include
│   │   └── lib_1
│   │       └── *.hpp
│   └── src
│       └── *.cpp
├── lib_2
│   ├── CMakeLists.txt
│   ├── include
│   │   └── lib_2
│   │       └── *.hpp
│   └── src
│       └── *.cpp
├── ...
└── app
    ├── CMakeLists.txt
    └── app.cpp

Imagen generada con inteligencia artificial para fines divulgativos

En una primera aproximación al problema, asumiremos la inexistencia de interdependencias entre las propias bibliotecas, punto éste que discutiremos al final del artículo.

2. Ficheros CMakeLists.txt para las bibliotecas internas

La estructura típica del fichero de configuración CMakeLists.txt para la biblioteca i-ésima lib_i será la siguiente:

   # sample_project/lib_i/CMakeLists.txt    add_library(lib_i) target_sources(lib_i PRIVATE src/impl_i_1.cpp src/impl_i_2.cpp # ... PUBLIC FILE_SET HEADERS BASE_DIRS include FILES include/lib_i/hdr_i_1.hpp include/lib_i/hdr_i_2.hpp # ... ) target_compile_features(lib_i PUBLIC cxx_std_**)

El comando add_library crea un target de biblioteca. Por su parte, el comando target_sources asocia los archivos fuente a lib_i y define su alcance de compilación: asigna las implementaciones (*.cpp) como dependencias privadas (PRIVATE) de construcción interna, y expone las cabeceras públicas (*.hpp) mediante PUBLIC FILE_SET HEADERS. Al especificar BASE_DIRS include, CMake identifica el directorio raíz asociado a las cabeceras públicas y puede utilizar dicha información durante la exportación e instalación del objetivo. Además, permite que los consumidores de la biblioteca incluyan las cabeceras mediante rutas como #include <lib_i/hdr_i_1.hpp>, #include <lib_i/hdr_i_2.hpp>, etcétera. 

El comando target_compile_features con el modificador PUBLIC establece el estándar C++ mínimo requerido por la biblioteca y, al mismo tiempo, propaga automáticamente dicha exigencia a cualquier consumidor que enlace con ella. Sustitúyase cxx_std_** con, por ejemplo, cxx_std_23, según las necesidades de la biblioteca.

Según las necesidades, pueden incorporarse comandos adicionales como target_compile_definitions o target_compile_options, entre otros. 
Según la terminología de CMake, una biblioteca puede ser de varios tipos, entre los cuales cabe citar:
  • STATIC: un archivo de ficheros objeto destinado a ser utilizado al enlazar otros objetivos (targets).
  • SHARED: una librería dinámica que puede ser enlazada por otros objetivos y cargada en tiempo de ejecución.
  • INTERFACE: un target de biblioteca de este tipo no compila archivos fuente y no produce un artefacto de biblioteca en el disco. Un caso de uso principal viene dado por las bibliotecas que sólo contienen archivos de cabecera (header-only).
De no especificarse explícitamente en add_library, el tipo de la biblioteca será STATIC o SHARED en función de si el valor de la variable BUILD_SHARED_LIBS queda definido, respectivamente, como OFF (por defecto) u ON.

3. Fichero CMakeLists.txt para el ejecutable

Por su parte, el fichero de configuración que situaremos en el directorio que contiene la aplicación app será:

   # sample_project/app/CMakeLists.txt    add_executable(app_name app.cpp)    target_link_libraries(app_name PRIVATE       lib_1       lib_2       # ...    )

Aquí, el comando target_link_libraries enlaza al ejecutable con las bibliotecas internas de las que depende.

4. Fichero raíz CMakeLists.txt

El fichero de configuración del proyecto, localizado en la raíz de su árbol de directorios, se reducirá a una mera inclusión de subdirectorios:

   # project/CMakeLists.txt    cmake_minimum_required(VERSION 4.0)    project(project_name VERSION 0.1.0 LANGUAGES CXX)    add_subdirectory(lib_1)    add_subdirectory(lib_2)    # ...    add_subdirectory(app)

5. Ejemplo práctico

A modo de ejemplo ilustrativo, consideremos un proyecto con una única biblioteca interna de nombre text que proporciona una función para la subdivisión (split) de una cadena de caracteres en tokens de acuerdo a una serie de delimitadores proporcionada por el usuario:

   // split_project/text/include/text/split.hpp #ifndef TEXT_LIBRARY_SPLIT_HPP #define TEXT_LIBRARY_SPLIT_HPP    #include <string>    #include <string_view>    #include <vector>    namespace text {       [[nodiscard]] auto split( std::string_view text,          std::string_view delims ) -> std::vector<std::string>;    } #endif // TEXT_LIBRARY_SPLIT_HPP

   // split_project/text/src/split.cpp    #include <text/split.hpp>    namespace text {       [[nodiscard]]  auto split( std::string_view text,          std::string_view delims ) -> std::vector<std::string>       {          using sz_t = std::string_view::size_type;          auto tokens = std::vector<std::string>{};          auto first = sz_t{};          while (first < text.length()) {             auto const last = text.find_first_of(delims, first);             if (first != last) {                tokens.emplace_back(text.substr(first, last - first)); }             if (last == std::string_view::npos) {                break; }             first = last + 1;          }          return tokens;       }    }

Podemos comprobar la correcta configuración de nuestro proyecto con la siguiente función principal:

   // split_project/app/app.cpp    #include <iostream>    #include <text/split.hpp>    auto main() -> int    {       for (std::string_view token : text::split("I am Spartacus!"" !")) {          std::cout << token << ','; }    }

Ello produciría como salida la secuencia: I,am,Spartacus,.

Sin más que adaptar el esquema general explicado anteriormente, la construcción completa de este proyecto puede describirse mediante los siguientes ficheros CMakeLists.txt:

   # split_project/CMakeLists.txt    cmake_minimum_required(VERSION 4.0)    project(split_test VERSION 0.1.0 LANGUAGES CXX)    add_subdirectory(text)    add_subdirectory(app)

   # split_project/text/CMakeLists.txt    add_library(text) target_sources(text PRIVATE src/split.cpp PUBLIC FILE_SET HEADERS BASE_DIRS include FILES include/text/split.hpp ) target_compile_features(text PUBLIC cxx_std_17)

   # split_project/app/CMakeLists.txt    add_executable(split_app app.cpp)    target_link_libraries(split_app PRIVATE       text    )

Observemos que split_project/app/CMakeLists.txt no necesita declarar explícitamente el requisito cxx_std_17. Al enlazar con la biblioteca text, hereda automáticamente dicho requisito porque la biblioteca lo declara como parte de su interfaz pública mediante target_compile_features(text PUBLIC cxx_std_17).

6. Dependencias entre bibliotecas internas

En caso de que la biblioteca lib_i haga uso de una o más bibliotecas lib_j, bastará añadir dicha dependencia en el fichero CMakeLists.txt asociado a la primera de ellas con un comando similar al siguiente:

   target_link_libraries(lib_i PRIVATE lib_j)

En efecto, target_link_libraries agregará automáticamente una dependencia en el build system para asegurarse de que lib_j esté actualizada antes de ser enlazada con lib_i.
Si lib_j únicamente se utiliza en la implementación interna de lib_i, la dependencia deberá declararse como PRIVATE. Si las cabeceras públicas de lib_i utilizan tipos definidos por lib_j, la dependencia deberá declararse como PUBLIC.

Referencias bibliográficas

  1. CMake Documentation (latest release) - https://cmake.org/cmake/help/latest/
  2. How to CMake Good - https://youtu.be/_yFPO1ofyF0?list=PLK6MXr8gasrGmIiSuVQXpfFuE1uPT615s
  3. Henry Schreiner - An Introduction to Modern CMake - https://cliutils.gitlab.io/modern-cmake/