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
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
|
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
)
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:
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.
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
- CMake Documentation (latest release) - https://cmake.org/cmake/help/latest/
- How to CMake Good - https://youtu.be/_yFPO1ofyF0?list=PLK6MXr8gasrGmIiSuVQXpfFuE1uPT615s
- Henry Schreiner - An Introduction to Modern CMake - https://cliutils.gitlab.io/modern-cmake/
