Saltar a contenido

Última actualización:2026-10-12

Construir proyectos C++ con Meson

Últimamente, al usar algunos proyectos de código abierto, he comprobado que todos adoptan el sistema de compilación meson+ninja y abandonan el enfoque de CMake y Makefile; este artículo introducirá Meson, desde su instalación hasta la escritura completa de un proyecto meson.

A continuación, entremos en materia.

1. Instalación de Meson

https://github.com/mesonbuild/meson

Meson está disponible en PyPi, por lo que puede instalarse con pip3 install meson. Asegúrate de usar el comando pip correcto en cada sistema y de emplear siempre la versión de pip de Python 3.

Si lo prefieres, también puedes instalarlo localmente con el comando estándar de Python:

python3 -m pip install meson

Si vas a construir con Ninja, puedes descargar Ninja directamente desde su página de releases de GitHub o a través de PyPi:

python3 -m pip install ninja

Para más información sobre la instalación de Meson build, consulta el sitio web oficial de Meson que se indica abajo.

https://mesonbuild.com/Getting-meson.html

2. Meson Vs Cmake

Si todo el mundo usa CMake, ¿por qué usar Meson? ¿Cuáles son las ventajas y desventajas de cada uno?

En cuanto al rendimiento, Meson suele ser conocido por su velocidad de compilación. El diseño de Meson busca optimizar la velocidad de construcción y destaca especialmente en las compilaciones incrementales. Meson usa Ninja como backend por defecto, y Ninja también es célebre por ser rápido y eficiente.

En comparación, la velocidad de compilación de CMake puede verse afectada por el tamaño del proyecto, sobre todo en proyectos grandes. CMake emplea varios generadores (como Makefile), y el rendimiento puede variar de un generador a otro.

Por supuesto, también se puede usar CMake + Ninja para acelerar la compilación.

Desde el punto de vista de los proyectos de código abierto, Meson tiene muchísimos casos de uso, por ejemplo: PostgreSQL, QEMU, GNOME Software, GTK+, etcétera

https://mesonbuild.com/Users.html

2.1 Ventajas y desventajas de Meson

1.Ventajas

  • Basado en Python, sencillo de usar
  • Admite compilación cruzada
  • Todos los objetos son fuertemente tipados: como los objetos son entidades como 'dependency' o 'include directory', es difícil cometer errores de sustitución de cadenas.
  • Resulta muy cómodo referenciar bibliotecas de terceros
  • Muy, muy rápido, especialmente en las compilaciones incrementales.
  • La documentación es n veces mejor que la de cmake.

http://mesonbuild.com

2.Desventajas

2.2 Ventajas y desventajas de CMake

1.Ventajas

  • Soporte IDE amigable y colaboración en equipo bastante buena
  • Maduro, es prácticamente el estándar de los proyectos C++

2.Inconvenientes

  • La compilación cruzada es difícil
  • La velocidad de compilación es relativamente lenta

3. Meson WrapDB

Como googletest no es un proyecto meson, ¿cómo hacer que meson dependa de él?

Del mismo modo, grpc y abseil-cpp se usan de forma parecida.

Aquí hay que introducir primero WrapDB: Meson WrapDB es un componente del sistema de compilación Meson que se encarga de gestionar y proporcionar los archivos de descripción de compilación de proyectos de terceros, habitualmente llamados archivos "wrap". Estos archivos wrap contienen la información necesaria para configurar y compilar proyectos de terceros, de modo que puedan integrarse fácilmente en otros proyectos que usan el sistema de compilación Meson.

Puedes consultar las bibliotecas de terceros con el comando search o en el sitio web que aparece abajo.

https://mesonbuild.com/Wrapdb-projects.html#meson-wrapdb-packages

Tomemos como ejemplo el uso de gtest:

  • Buscar gtest
meson wrap search gtest

Si este comando devuelve el nombre del proyecto empaquetado, ya se puede pasar al siguiente paso:

  • Crear el directorio subprojects en tu propio proyecto e instalar gtest con install
mkdir -p subprojects
meson wrap install gtest

En este momento, todas las bibliotecas de terceros de las que depende tu proyecto ya están instaladas en el directorio subprojects.

➜  gtest_div tree -L 1 subprojects
subprojects
├── gtest.wrap

Ahora se genera gtest.wrap, cuyo contenido incluye lo siguiente.

Nota: este archivo se genera tras el install anterior; se recomienda no modificarlo.

[wrap-file]
directory = googletest-1.14.0
source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
source_filename = gtest-1.14.0.tar.gz
source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7
patch_filename = gtest_1.14.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch
patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz
wrapdb_version = 1.14.0-1

[provide]
gtest = gtest_dep
gtest_main = gtest_main_dep
gmock = gmock_dep
gmock_main = gmock_main_dep

4. ¿Cómo escribir un proyecto meson?

Llegamos a la parte práctica: ¿cómo se escribe un proyecto meson?

A continuación, un pequeño proyecto que usa gtest para pruebas unitarias, tomando como ejemplo la prueba de la división a/b.

4.1 Escribir el código

double divide(double a, double b);

Coloca el .h y el .cc en el directorio src:

src
├── division.cc
└── division.h

A continuación, crea el directorio test al mismo nivel.

test
└── division_test.cc

4.2 Gestión del proyecto con meson

Añade el archivo meson.build en la raíz del proyecto, con el siguiente contenido:

project('gtest_div', 'cpp')
gtest_proj = subproject('gtest')
gtest_dep = gtest_proj.get_variable('gtest_main_dep') 
div_src = files('src/division.cc')
tests_src = files('test/division_test.cc')
e = executable(
    'division_test',
    tests_src + div_src, 
    dependencies : [ gtest_dep],
    override_options : ['cpp_std=c++14']
)
# https://mesonbuild.com/Unit-tests.html#testing-tool
test('this is a division test', e)

Se puede ver que este contenido coincide básicamente con la sintaxis de python; quien conozca python leerá este código con mucha facilidad. Aquí destacamos las partes más importantes del mismo.

  • Introducir bibliotecas de terceros

Por ejemplo: si el proyecto de la biblioteca de terceros que introduces se llama gtest, así:

project('gtest', 'cpp',
  version : '1.14.0',
  default_options : ['cpp_std=c++14'],
  license : 'bsd')

Entonces puedes obtener ese project mediante subproject y, con gtest_proj.get_variable, conseguir la información de dependencia de gtest; por ejemplo, en googletest se declara así:

gtest_main_dep = declare_dependency(
    include_directories : gtest_incdir,
    sources : [gtest_libsources, gtest_mainsources],
    dependencies : dependency('threads')
)
  • executable

executable es muy similar al add_executable de cmake: genera el ejecutable division_test.

  • test

Sintaxis de las pruebas unitarias:

e = executable('prog', 'testprog.c')
test('name of test', e)

Se puede usar meson test o ninja test; al terminar la ejecución se genera un registro de pruebas, ubicado en:

build/meson-logs/testlog.txt

4.3 Construir y ejecutar el proyecto Meson

Con lo anterior, un proyecto meson está listo. A continuación, construimos y ejecutamos este proyecto.

4.3.1 Configurar el directorio de compilación

Esto es similar al mkdir build && cmake .. de cmake.

meson setup build

La ejecución es la siguiente:

➜  gtest_div meson setup build               
The Meson build system
Version: 1.2.3
Source dir: /Users/light/work/practice/gtest_div
Build dir: /Users/light/work/practice/gtest_div/build
Build type: native build
Project name: gtest_div
Project version: undefined
C++ compiler for the host machine: c++ (clang 12.0.5 "Apple clang version 12.0.5 (clang-1205.0.22.11)")
C++ linker for the host machine: c++ ld64 650.9
Host machine cpu family: x86_64
Host machine cpu: x86_64
Downloading gtest source from https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
Download size: 867764
Downloading: ..........
Downloading gtest patch from https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch
Download size: 2440
Downloading: ..........

Executing subproject gtest 

gtest| Project name: gtest
gtest| Project version: 1.14.0
gtest| C++ compiler for the host machine: c++ (clang 12.0.5 "Apple clang version 12.0.5 (clang-1205.0.22.11)")
gtest| C++ linker for the host machine: c++ ld64 650.9
gtest| Run-time dependency threads found: YES
gtest| Dependency threads found: YES unknown (cached)
gtest| Dependency threads found: YES unknown (cached)
gtest| Dependency threads found: YES unknown (cached)
gtest| Build targets in project: 0
gtest| Subproject gtest finished.

Build targets in project: 1

gtest_div undefined

  Subprojects
    gtest: YES

Found ninja-1.11.1 at /usr/local/bin/ninja

En este momento podemos ver en subprojects el proyecto gtest en caché.

subprojects
├── googletest-1.14.0  # here
├── gtest.wrap
└── packagecache # here

4.3.2 Compilar

Podemos usar los dos comandos siguientes; el primero:

meson compile -C build/

Registro de ejecución:

➜  gtest_div meson compile -C build/            
INFO: autodetecting backend as ninja
INFO: calculating backend command to run: /usr/local/bin/ninja -C /Users/light/work/practice/gtest_div/build
ninja: Entering directory `/Users/light/work/practice/gtest_div/build'
[5/5] Linking target division_test

El segundo:

➜  gtest_div ninja -C build
ninja: Entering directory `build'
[5/5] Linking target division_test

En esencia, estos dos comandos son lo mismo: ambos compilan con ninja. Por supuesto, también puedes omitir el directorio con -C, y en ese caso tendrás que hacer cd build && ninja o cd build && meson compile.

4.3.3 Ejecutar y probar

Ahora se genera el ejecutable division_test, cuya ejecución es la siguiente:

➜  gtest_div ./build/division_test
Running main() from ../subprojects/googletest-1.14.0/googletest/src/gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from DivisionTest
[ RUN      ] DivisionTest.Divide
[       OK ] DivisionTest.Divide (0 ms)
[ RUN      ] DivisionTest.DivideByZero
[       OK ] DivisionTest.DivideByZero (0 ms)
[----------] 2 tests from DivisionTest (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 2 tests.

Como en meson.build añadimos la prueba unitaria test, podemos usar

meson test

o:

ninja test

Si se especifica el directorio:

meson test -C build/

or 

ninja test -C build/

Registro de ejecución:

➜  gtest_div meson test -C build 
ninja: Entering directory `/Users/light/work/practice/gtest_div/build'
ninja: no work to do.
1/1 this is a division test        OK              0.01s

Ok:                 1   
Expected Fail:      0   
Fail:               0   
Unexpected Pass:    0   
Skipped:            0   
Timeout:            0   

Full log written to /Users/light/work/practice/gtest_div/build/meson-logs/testlog.txt

Temas relacionados

Comentarios