00 Neden Meson
CMake karmaşık sözdizimi, Autotools ise yavaş yapılandırma süreci ile bilinir. Meson her ikisinin sorunlarını çözerek gömülü Linux projelerinde modern bir deneyim sunar.
Üç sistemin karşılaştırması
| Özellik | Meson | CMake | Autotools |
|---|---|---|---|
| Sözdizimi | Python benzeri, temiz DSL | Tuhaf prosedürel dil | M4 makro karmaşası |
| Yapılandırma hızı | Çok hızlı (Ninja backend) | Orta | Yavaş (./configure uzun sürer) |
| Build hızı | Ninja ile paralel, en hızlı | Make veya Ninja seçilebilir | Make, rekabetçi değil |
| Cross-compile | cross-file ile yerleşik destek | Toolchain dosyası, karmaşık | --host flag, genellikle kırılır |
| Bağımlılık yönetimi | pkg-config + wrap sistemi | find_package, tutarsız | pkg-config elle yazılır |
| Test entegrasyonu | Yerleşik test(), benchmark() | CTest ayrı araç | make check, elle yazılır |
| Yocto desteği | meson.bbclass — resmi | cmake.bbclass — resmi | autotools.bbclass — eski |
| compile_commands.json | Otomatik üretilir | Flag gerekir | bear ile elle üretilir |
| Öğrenme eğrisi | Düz — 1 günde öğrenilebilir | Dik — CMake 3 ile daha iyi | Çok dik — aylar alır |
Meson'ın gömülü avantajları
Meson kurulumu
# pip ile güncel sürüm (önerilen)
pip3 install --user meson ninja
# Debian/Ubuntu paket yöneticisi (eski sürüm olabilir)
sudo apt-get install meson ninja-build
# Sürüm kontrolü
meson --version # 1.3.0+
ninja --version # 1.11+
01 meson.build temelleri
Her Meson projesi kök dizinde bir meson.build dosyasıyla başlar. Bu dosya hedefleri, bağımlılıkları ve derleme seçeneklerini tanımlar.
Proje tanımı
project('my-daemon', 'c',
version : '1.2.0',
license : 'GPL-2.0-only',
default_options : [
'c_std=c11',
'warning_level=2',
'werror=false',
'buildtype=debugoptimized',
'strip=false',
]
)
Executable hedefi
sources = files(
'src/main.c',
'src/config.c',
'src/sensor.c',
'src/network.c',
)
executable('my-daemon',
sources,
include_directories : include_directories('include'),
install : true,
install_dir : get_option('sbindir'),
)
Kütüphane hedefleri
# Paylaşımlı kütüphane
libmyutil = shared_library('myutil',
files('lib/util.c', 'lib/crc.c'),
version : '1.0.0',
soversion : '1',
install : true,
)
# Statik kütüphane
libmyutil_static = static_library('myutil',
files('lib/util.c', 'lib/crc.c'),
pic : true, # cross ile shared olarak da link edilebilsin
)
# Hem shared hem static üreten kısayol
both_libraries('myutil',
files('lib/util.c', 'lib/crc.c'),
install : true,
)
Bağımlılık yönetimi
# pkg-config ile sistem bağımlılıkları
libgpiod_dep = dependency('libgpiod', version : '>=2.0')
libudev_dep = dependency('libudev')
libdbus_dep = dependency('dbus-1', required : false)
# Koşullu bağımlılık
if libdbus_dep.found()
sources += files('src/dbus_iface.c')
endif
# Dahili include
inc = include_directories('include', 'third_party/jsmn')
executable('my-daemon',
sources,
dependencies : [libgpiod_dep, libudev_dep, libdbus_dep],
include_directories : inc,
install : true,
)
Compiler flag'leri ve feature check
cc = meson.get_compiler('c')
# Özellik tespiti
has_epoll = cc.has_function('epoll_create1',
prefix : '#include <sys/epoll.h>')
has_timerfd = cc.has_header('sys/timerfd.h')
# Koşullu derleme
conf = configuration_data()
conf.set('HAVE_EPOLL', has_epoll ? 1 : 0)
conf.set('HAVE_TIMERFD', has_timerfd ? 1 : 0)
conf.set_quoted('VERSION', meson.project_version())
configure_file(
input : 'config.h.in',
output : 'config.h',
configuration : conf,
)
# Ekstra flag ekle
extra_flags = cc.get_supported_arguments([
'-fstack-protector-strong',
'-D_FORTIFY_SOURCE=2',
'-Wformat-security',
])
add_project_arguments(extra_flags, language : 'c')
02 Cross-compilation
Meson, cross-compilation için ayrı bir "cross file" mekanizması kullanır. Bu dosya toolchain, sysroot ve hedef sisteme ait ayarları tanımlar.
Cross file yapısı
# aarch64-linux-gnu.ini
[binaries]
c = 'aarch64-linux-gnu-gcc'
cpp = 'aarch64-linux-gnu-g++'
ar = 'aarch64-linux-gnu-ar'
strip = 'aarch64-linux-gnu-strip'
objcopy = 'aarch64-linux-gnu-objcopy'
pkg-config = 'aarch64-linux-gnu-pkg-config'
[built-in options]
c_args = ['-march=armv8-a', '-mtune=cortex-a53']
c_link_args = ['-Wl,--hash-style=gnu']
[properties]
sys_root = '/opt/sysroot-aarch64'
pkg_config_libdir = '/opt/sysroot-aarch64/usr/lib/pkgconfig'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'cortex-a53'
endian = 'little'
Cross file ile yapılandırma
# Cross build dizini oluştur
meson setup builddir-aarch64 \
--cross-file aarch64-linux-gnu.ini \
--buildtype release \
-Dprefix=/usr
# Derleme
ninja -C builddir-aarch64 -j$(nproc)
# Kurulum (DESTDIR ile sysroot'a)
DESTDIR=/opt/sysroot-aarch64 ninja -C builddir-aarch64 install
Sysroot ve pkg-config entegrasyonu
# Cross sysroot pkg-config wrapper scripti
cat > /usr/local/bin/aarch64-linux-gnu-pkg-config << 'EOF'
#!/bin/sh
SYSROOT=/opt/sysroot-aarch64
export PKG_CONFIG_DIR=
export PKG_CONFIG_LIBDIR=${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=${SYSROOT}
exec pkg-config "$@"
EOF
chmod +x /usr/local/bin/aarch64-linux-gnu-pkg-config
Native ve cross hedeflerin birlikte yönetimi
># meson.build — kod üreteci host'ta çalışır, output hedef için derlenir
gen_tool = executable('gen-headers',
'tools/gen_headers.c',
native : true, # host makinede derle
)
gen_headers = custom_target('generated-headers',
input : ['defs/registers.json'],
output : ['registers.h'],
command : [gen_tool, '@INPUT@', '@OUTPUT@'],
)
executable('firmware-util',
['src/main.c', gen_headers], # cross-compile hedef
install : true,
)
RISC-V 32-bit cross file örneği
# riscv32-unknown-linux-gnu.ini
[binaries]
c = 'riscv32-unknown-linux-gnu-gcc'
cpp = 'riscv32-unknown-linux-gnu-g++'
ar = 'riscv32-unknown-linux-gnu-ar'
pkg-config = 'pkg-config'
[built-in options]
c_args = ['-march=rv32imac', '-mabi=ilp32']
[properties]
sys_root = '/opt/riscv32-sysroot'
pkg_config_libdir = '/opt/riscv32-sysroot/usr/lib/pkgconfig'
[host_machine]
system = 'linux'
cpu_family = 'riscv32'
cpu = 'riscv32'
endian = 'little'
03 Subproject ve wrap
Meson'ın wrap sistemi, harici bağımlılıkları proje ağacına dahil etmenin standart yoludur. Sistem kütüphanesi bulunamazsa otomatik olarak kaynak kodundan derler.
wrap dosyası formatı
# subprojects/libsodium.wrap
[wrap-file]
directory = libsodium-1.0.19
source_url = https://download.libsodium.org/libsodium/releases/libsodium-1.0.19.tar.gz
source_hash = 018d79fe0a045cca07331d37bd0cb57b91bbc7fd85952a6d84c0ae382e898e98
patch_url = https://wrapdb.mesonbuild.com/v2/libsodium_1.0.19-1/get_patch
patch_hash = abc123...
[provide]
libsodium = libsodium_dep
wrapdb'den bağımlılık kurma
># Mevcut wrap paketlerini listele
meson wrap list
# Belirli paketi indir
meson wrap install zlib
meson wrap install libuuid
meson wrap install nlohmann_json
# subprojects/ dizini oluşturulur
ls subprojects/
# zlib.wrap libuuid.wrap nlohmann_json.wrap
meson.build'e fallback bağımlılık
# Sistem kütüphanesi yoksa subproject kullan
zlib_dep = dependency('zlib',
required : true,
fallback : ['zlib', 'zlib_dep'],
)
# Gömülü ortamda her zaman subproject kullan
libjpeg_dep = dependency('libjpeg',
required : false,
fallback : ['libjpeg-turbo', 'libjpeg_dep'],
)
if not libjpeg_dep.found()
error('libjpeg-turbo bulunamadi')
endif
Git submodule olarak subproject
># subprojects/mylib/ dizinine git submodule ekle
git submodule add https://github.com/example/mylib.git subprojects/mylib
# subprojects/mylib.wrap (git tabanlı)
[wrap-git]
url = https://github.com/example/mylib.git
revision = v2.1.0
depth = 1
[provide]
mylib = mylib_dep
Subproject'te override
># subprojects/mynewlib/meson.build
project('mynewlib', 'c')
mynewlib_lib = static_library('mynewlib',
'src/mynewlib.c',
)
# Ana projeye dışa aktar
mynewlib_dep = declare_dependency(
include_directories : include_directories('include'),
link_with : mynewlib_lib,
)
# Ana proje meson.build'de
subproject('mynewlib')
mynewlib_dep = dependency('mynewlib')
04 Testler ve benchmark
Meson'ın yerleşik test çerçevesi, birim testlerini ve performans benchmark'larını aynı yapılandırma içinde yönetir.
Test tanımı
# Birim test oluştur
test_sensor = executable('test-sensor',
'tests/test_sensor.c',
include_directories : inc,
link_with : libmyutil,
dependencies : [cmocka_dep],
)
test('sensor birim testi',
test_sensor,
args : ['--verbose'],
timeout : 30,
env : ['SENSOR_MOCK=1'],
)
# Parametrik test
foreach driver : ['spi', 'i2c', 'uart']
test('driver test @0@'.format(driver),
test_sensor,
args : ['--driver', driver],
)
endforeach
Test çalıştırma
># Tüm testleri çalıştır
meson test -C builddir
# Belirli test
meson test -C builddir 'sensor birim testi'
# Paralel çalıştır, verbose
meson test -C builddir --num-processes 4 -v
# Başarısız testleri tekrar çalıştır
meson test -C builddir --rerun-failing
# JUnit XML çıktısı (CI için)
meson test -C builddir --logbase testresults
# builddir/meson-logs/testresults.junit.xml üretilir
cmocka entegrasyonu
># subprojects/cmocka.wrap kurulu
cmocka_dep = dependency('cmocka',
required : true,
fallback : ['cmocka', 'cmocka_dep'],
)
test_config = executable('test-config',
'tests/test_config.c',
'src/config.c',
include_directories : inc,
dependencies : cmocka_dep,
)
test('config modulu',
test_config,
protocol : 'tap', # TAP protokolu ciktisi
)
Benchmark tanımı
bench_crc = executable('bench-crc',
'benchmarks/bench_crc.c',
link_with : libmyutil,
)
benchmark('CRC hesaplama',
bench_crc,
args : ['--iterations', '100000'],
timeout : 120,
)
# Benchmark çalıştır
meson test -C builddir --benchmark --logbase benchresults
QEMU üzerinde cross-test çalıştırma
># cross file'a ekle
[properties]
exe_wrapper = ['qemu-aarch64', '-L', '/opt/sysroot-aarch64']
# Artık meson test cross binary'leri QEMU üzerinde çalıştırır
meson test -C builddir-aarch64
05 Install hedefleri
Meson'ın install sistemi, binary'leri, kütüphaneleri, başlık dosyalarını, systemd servislerini ve veri dosyalarını doğru dizinlere yerleştirir.
Temel install yapılandırması
># meson setup ile prefix belirle
meson setup builddir \
--prefix /usr \
--libdir /usr/lib \
--sysconfdir /etc \
--localstatedir /var
# DESTDIR ile staging area'ya kur
DESTDIR=/tmp/staging ninja -C builddir install
# Kurulacak dosyaları listele
ninja -C builddir install 2>&1 | grep "Installing"
Farklı hedef türlerini kurma
# Binary
executable('my-daemon', sources,
install : true,
install_dir : get_option('sbindir'), # /usr/sbin
)
# Kütüphane
shared_library('mylib', lib_sources,
install : true,
# otomatik olarak libdir'e gider: /usr/lib
)
# Başlık dosyaları
install_headers(
'include/mylib.h',
'include/mylib_types.h',
subdir : 'mylib', # /usr/include/mylib/
)
# Veri dosyaları
install_data(
'data/default.conf',
install_dir : get_option('sysconfdir') / 'my-daemon',
)
# Man sayfaları
install_man('docs/my-daemon.8')
# Dizin oluştur
install_emptydir('/var/lib/my-daemon')
systemd servis dosyası kurulumu
># systemd birim dosyasını yapılandır ve kur
systemd = dependency('systemd', required : false)
if systemd.found()
systemd_system_unit_dir = systemd.get_variable(
pkgconfig : 'systemdsystemunitdir'
)
install_data(
'init/my-daemon.service',
install_dir : systemd_system_unit_dir,
)
endif
meson.options — kullanıcı seçenekleri
># meson.options (eski: meson_options.txt)
option('enable-dbus',
type : 'boolean',
value : true,
description : 'D-Bus arabirimi derle',
)
option('log-level',
type : 'combo',
choices : ['debug', 'info', 'warning', 'error'],
value : 'info',
description : 'Varsayılan log seviyesi',
)
option('max-sensors',
type : 'integer',
min : 1,
max : 64,
value : 16,
description : 'Maksimum sensör sayısı',
)
># Seçenekleri meson.build'de oku
if get_option('enable-dbus')
sources += files('src/dbus_iface.c')
deps += libdbus_dep
endif
conf.set('LOG_LEVEL_DEFAULT', '"' + get_option('log-level') + '"')
conf.set('MAX_SENSORS', get_option('max-sensors'))
# Yapılandırma zamanı override
meson setup builddir -Denable-dbus=false -Dlog-level=debug
06 Yocto ve Buildroot entegrasyonu
Meson projeleri hem Yocto'nun meson.bbclass'ı hem de Buildroot'un meson.mk'ı ile sorunsuz entegre olur. Otomatik cross-file üretimi sayesinde ek yapılandırma gerekmez.
Yocto — meson.bbclass
># meta-myproject/recipes-app/my-daemon/my-daemon_1.2.0.bb
SUMMARY = "Gömülü sensör daemon"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://COPYING;md5=..."
SRC_URI = "git://github.com/myorg/my-daemon.git;branch=main"
SRCREV = "abc123def456..."
S = "${WORKDIR}/git"
inherit meson pkgconfig systemd
# Meson seçenekleri
EXTRA_OEMESON = " \
-Denable-dbus=true \
-Dlog-level=info \
-Dmax-sensors=32 \
"
# Yocto meson.bbclass otomatik olarak şunları yapar:
# 1. MACHINE'e uygun cross-file üretir
# 2. PKG_CONFIG_SYSROOT_DIR ayarlar
# 3. do_configure: meson setup
# 4. do_compile: ninja
# 5. do_install: DESTDIR ile ninja install
SYSTEMD_SERVICE:${PN} = "my-daemon.service"
FILES:${PN} += "${sysconfdir}/my-daemon/"
meson.bbclass cross-file üretimi
Yocto, derleme zamanında hedef mimariye uygun bir cross-file otomatik oluşturur. Bu dosya ${WORKDIR}/meson.cross konumunda saklanır ve meson setup çağrısına otomatik eklenir.
># Yocto'nun ürettiği meson.cross örneği
[binaries]
c = '/path/to/sysroots/x86_64/usr/bin/aarch64-poky-linux/aarch64-poky-linux-gcc'
cpp = '/path/to/sysroots/x86_64/usr/bin/aarch64-poky-linux/aarch64-poky-linux-g++'
ar = '/path/to/sysroots/x86_64/usr/bin/aarch64-poky-linux/aarch64-poky-linux-ar'
pkgconfig = '/path/to/sysroots/x86_64/usr/bin/pkg-config'
[properties]
sys_root = '/path/to/sysroots/cortexa53-poky-linux'
pkg_config_libdir = '/path/to/sysroots/cortexa53-poky-linux/usr/lib/pkgconfig'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'cortex-a53'
endian = 'little'
Buildroot — package/my-daemon/
># package/my-daemon/Config.in
config BR2_PACKAGE_MY_DAEMON
bool "my-daemon"
depends on BR2_TOOLCHAIN_HAS_THREADS
select BR2_PACKAGE_LIBGPIOD
help
Gomulu sensor daemon.
># package/my-daemon/my-daemon.mk
MY_DAEMON_VERSION = 1.2.0
MY_DAEMON_SITE = https://github.com/myorg/my-daemon/archive
MY_DAEMON_SOURCE = v$(MY_DAEMON_VERSION).tar.gz
MY_DAEMON_DEPENDENCIES = libgpiod host-pkgconf
MY_DAEMON_CONF_OPTS = \
-Denable-dbus=false \
-Dmax-sensors=16
$(eval $(meson-package))
Buildroot meson-package makrosu davranışı
07 Introspection ve IDE desteği
Meson, build sistemi hakkında makine tarafından okunabilir JSON meta verisi üretir. Bu özellik clangd, VS Code ve diğer IDE'lerin cross-compile projelerini anlamasını sağlar.
compile_commands.json
># Meson her zaman otomatik üretir
meson setup builddir --cross-file aarch64.ini
ls builddir/compile_commands.json # hemen mevcut
# İçerik örneği
# [
# {
# "directory": "/home/user/project/builddir",
# "command": "aarch64-linux-gnu-gcc -I../include -march=armv8-a
# -c ../src/sensor.c -o src/sensor.c.o",
# "file": "/home/user/project/src/sensor.c"
# },
# ...
# ]
meson introspect
># Tüm hedefleri listele
meson introspect --targets builddir
# Bağımlılıkları listele
meson introspect --dependencies builddir
# Build seçeneklerini listele
meson introspect --buildoptions builddir
# Kaynak dosyaları
meson introspect --scan-dependencies builddir
# JSON formatı, jq ile işlenebilir
meson introspect --targets builddir | jq '.[].name'
VS Code için compile_commands.json bağlantısı
># Proje kökünde sembolik link oluştur
ln -s builddir/compile_commands.json compile_commands.json
# .vscode/settings.json
{
"clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}",
"--background-index",
"--clang-tidy"
]
}
Meson ile IDE projeleri
># VS Code görev entegrasyonu — .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "meson: build (aarch64)",
"type": "shell",
"command": "ninja",
"args": ["-C", "builddir-aarch64"],
"group": "build"
},
{
"label": "meson: test",
"type": "shell",
"command": "meson",
"args": ["test", "-C", "builddir-aarch64", "-v"],
"group": "test"
}
]
}
meson rewrite — programatik düzenleme
># meson.build'i elle düzenlemeden kaynak ekle
meson rewrite target my-daemon add src/new_module.c
# Bağımlılık ekle
meson rewrite kwargs set target my-daemon dependencies libssl_dep
# Seçenek değiştir
meson configure builddir -Dlog-level=debug
08 Gerçek proje örneği — gömülü C daemon
Uçtan uca örnek: GPIO ve SPI ile çalışan, systemd ile entegre, D-Bus arabirimi olan gömülü bir sensör daemon'ı için eksiksiz Meson yapılandırması.
Proje dizin yapısı
sensor-daemon/
├── meson.build # Kök yapılandırma
├── meson.options
├── include/
│ ├── sensor.h
│ └── config.h.in
├── src/
│ ├── main.c
│ ├── sensor_spi.c
│ ├── sensor_gpio.c
│ ├── config.c
│ └── dbus_iface.c
├── lib/
│ ├── meson.build # Alt yapılandırma
│ ├── crc.c
│ └── ringbuf.c
├── tests/
│ ├── meson.build
│ ├── test_sensor.c
│ └── test_config.c
├── data/
│ └── sensor-daemon.conf
├── init/
│ └── sensor-daemon.service
└── subprojects/
└── cmocka.wrap
Kök meson.build
project('sensor-daemon', 'c',
version : '2.0.0',
license : 'GPL-2.0-only',
default_options : [
'c_std=c11',
'warning_level=2',
'buildtype=debugoptimized',
]
)
cc = meson.get_compiler('c')
# Bağımlılıklar
libgpiod_dep = dependency('libgpiod', version : '>=2.0')
libudev_dep = dependency('libudev')
libsystemd_dep = dependency('libsystemd', required : false)
libdbus_dep = dependency('dbus-1',
required : get_option('enable-dbus'),
fallback : [],
)
# Config header üretimi
conf = configuration_data()
conf.set_quoted('PACKAGE_VERSION', meson.project_version())
conf.set('HAVE_SD_NOTIFY', libsystemd_dep.found() ? 1 : 0)
conf.set('HAVE_DBUS', libdbus_dep.found() ? 1 : 0)
conf.set('MAX_SENSORS', get_option('max-sensors'))
conf.set_quoted('LOG_LEVEL', get_option('log-level'))
conf.set_quoted('DEFAULT_CONFIG_PATH',
get_option('sysconfdir') / 'sensor-daemon' / 'sensor-daemon.conf')
configure_file(
input : 'include/config.h.in',
output : 'config.h',
configuration : conf,
)
generated_inc = include_directories('.')
# Alt dizinler
subdir('lib') # libsensorutil oluşturur
subdir('tests') # testleri tanımlar
# Ana kaynak listesi
daemon_sources = files(
'src/main.c',
'src/sensor_spi.c',
'src/sensor_gpio.c',
'src/config.c',
)
daemon_deps = [libgpiod_dep, libudev_dep]
if libsystemd_dep.found()
daemon_sources += files('src/sd_notify.c')
daemon_deps += libsystemd_dep
endif
if libdbus_dep.found()
daemon_sources += files('src/dbus_iface.c')
daemon_deps += libdbus_dep
endif
executable('sensor-daemon',
daemon_sources,
include_directories : [
include_directories('include'),
generated_inc,
],
link_with : libsensorutil,
dependencies : daemon_deps,
install : true,
install_dir : get_option('sbindir'),
)
install_data('data/sensor-daemon.conf',
install_dir : get_option('sysconfdir') / 'sensor-daemon',
)
systemd = dependency('systemd', required : false)
if systemd.found()
install_data('init/sensor-daemon.service',
install_dir : systemd.get_variable(pkgconfig : 'systemdsystemunitdir'),
)
endif
lib/meson.build
libsensorutil = static_library('sensorutil',
files('crc.c', 'ringbuf.c'),
include_directories : include_directories('../include'),
pic : true,
)
tests/meson.build
cmocka_dep = dependency('cmocka',
required : true,
fallback : ['cmocka', 'cmocka_dep'],
)
test_env = environment()
test_env.set('SENSOR_MOCK_PATH', meson.project_source_root() / 'tests/fixtures')
foreach t : ['sensor', 'config']
test_exe = executable('test-@0@'.format(t),
'test_@0@.c'.format(t),
include_directories : [
include_directories('../include'),
generated_inc,
],
link_with : libsensorutil,
dependencies : cmocka_dep,
)
test('test @0@'.format(t),
test_exe,
env : test_env,
timeout : 60,
protocol : 'tap',
)
endforeach
Tam derleme ve kurulum
># Host derleme (geliştirme)
meson setup builddir
ninja -C builddir
meson test -C builddir -v
# ARM64 cross-compile + kurulum
meson setup builddir-aarch64 \
--cross-file aarch64-linux-gnu.ini \
--buildtype release \
-Dprefix=/usr \
-Denable-dbus=true \
-Dmax-sensors=32
ninja -C builddir-aarch64 -j$(nproc)
DESTDIR=/tmp/rootfs-staging ninja -C builddir-aarch64 install
# Kurulan dosyaları listele
find /tmp/rootfs-staging -type f | sort