Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Meson: система сборки

Meson: система сборки, описание проекта и интеграция зависимостей

Как работать с Meson: файл meson.build, цели сборки, поиск зависимостей, параметры проекта, подкаталоги и запуск тестов.

Прямой ответ

Meson описывает проект декларативно и генерирует файлы для Ninja. Зависимости находятся по имени, а параметры сборки задаются при конфигурации.

Файл Назначение
meson.build Описание проекта
build.ninja Сгенерированные правила
meson_options.txt Параметры проекта
project('service', 'cpp',
  version : '1.2.0',
  default_options : ['cpp_std=c++20', 'warning_level=3'])

fmt_dep = dependency('fmt', required : true)

executable('service',
  'src/main.cpp',
  dependencies : [fmt_dep],
  install : true)

Описание определяет состав целей, поэтому правила сборки генерируются автоматически.

Описание проекта и состав целей

Описание определяет состав целей, поэтому правила сборки генерируются по описанию.

Объявление проекта. Определяет язык и версию, поэтому состав задан.

Цель исполняемого файла. Определяет состав источников, поэтому сборка выполняется по списку.

Цель библиотеки. Определяет экспортируемые заголовки, поэтому подключение задано.

project('service', 'cpp',
  version : '1.2.0',
  default_options : ['cpp_std=c++20'])

lib_service = library('service_core',
  'src/core.cpp',
  include_directories : include_directories('include'),
  install : true)

executable('service',
  'src/main.cpp',
  link_with : lib_service)

Практическое следствие: состав источников определяется описанием, поэтому добавление файла требует правки описания.

Поиск зависимостей и его способы

Зависимость находится по имени, поэтому состав задан описанием.

Системный пакет. Находится по имени, поэтому установка выполняется системой.

Собственная зависимость. Описывается в подкаталоге, поэтому состав задан проектом.

Обязательность. Определяет поведение при отсутствии, поэтому сборка завершается явно.

# Системный пакет
fmt_dep = dependency('fmt', version : '>=10.0', required : true)

# Необязательная зависимость
curl_dep = dependency('libcurl', required : false)

if curl_dep.found()
  add_project_arguments('-DHAVE_CURL', language : 'cpp')
endif

# Собственная зависимость из подкаталога
subdir('third_party/json')

Практическое следствие: необязательная зависимость определяет состав сборки, поэтому условие задаёт поведение. О сравнении способов подключения — в статье про управление версиями зависимостей C++.

Параметры проекта и их назначение

Параметры определяют состав сборки, поэтому вариант задаётся при конфигурации.

Объявление параметра. Определяет допустимые значения, поэтому вариант задан явно.

Значение по умолчанию. Определяет поведение без указания, поэтому конфигурация упрощена.

Переопределение. Выполняется при конфигурации, поэтому описание не меняется.

# meson_options.txt
option('enable_tests', type : 'boolean', value : true,
       description : 'Сборка тестов')
option('log_level', type : 'combo',
       choices : ['debug', 'info', 'warn'], value : 'info',
       description : 'Уровень журнала')
# meson.build использует параметры
if get_option('enable_tests')
  subdir('tests')
endif

add_project_arguments('-DLOG_LEVEL_' + get_option('log_level').to_upper(),
                      language : 'cpp')
# Конфигурация с переопределением параметра
# meson setup build -Denable_tests=false -Dlog_level=warn

Практическое следствие: параметры определяют состав сборки, поэтому вариант задаётся без изменения описания.

Подкаталоги и их роль

Подкаталог описывает собственную часть проекта, поэтому состав локализован.

Описание подкаталога. Определяет состав целей, поэтому правка локализована.

Переменные подкаталога. Доступны родителю, поэтому состав связан.

Условное подключение. Определяется параметром, поэтому состав задаётся при конфигурации.

# tests/meson.build
gtest_dep = dependency('gtest', main : true, required : false)

if gtest_dep.found()
  test('core_tests',
    executable('core_tests', 'core_test.cpp',
               dependencies : [gtest_dep],
               link_with : lib_service))
endif
# Корневой meson.build подключает подкаталог
if get_option('enable_tests')
  subdir('tests')
endif

Практическое следствие: описание в подкаталоге локализует правку, поэтому изменение состава не затрагивает корневое описание.

Запуск тестов и их состав

Тесты объявляются описанием, поэтому запуск выполняется одной командой.

Объявление теста. Определяет исполняемый файл, поэтому состав задан.

Параллельный запуск. Определяется параметром, поэтому время сокращается.

Фильтр тестов. Определяет состав запуска, поэтому проверка локализована.

test('unit', unit_tests_exe, timeout : 60)
test('integration', integration_tests_exe, is_parallel : false)
# Запуск всех тестов
# meson test -C build

# Запуск с фильтром
# meson test -C build unit

# Подробный вывод
# meson test -C build -v

Практическое следствие: объявление тестов описанием обеспечивает единый запуск, поэтому проверка выполняется без отдельной команды.

Сравнение способов подключения зависимости

Способ подключения определяет состав настройки, поэтому выбор задан характером зависимости.

Системный пакет. Находится по имени, поэтому установка выполняется системой.

Собственная зависимость. Описывается в подкаталоге, поэтому состав задан проектом.

Внешний менеджер пакетов. Устанавливает зависимость, поэтому описание проекта не меняется.

# Системный пакет
fmt_dep = dependency('fmt', version : '>=10.0', required : true)

# Необязательная зависимость
curl_dep = dependency('libcurl', required : false)

# Собственная зависимость из подкаталога
subdir('third_party/json')
# Внешний менеджер пакетов
# conan install . --output-folder=build --build=missing
# meson setup build --wipe

Практическое следствие: внешний менеджер пакетов устанавливает зависимость без изменения описания, поэтому интеграция минимальна. О менеджерах пакетов — в статье про Conan.

Проверка конфигурации и её состава

Проверка выполняется после конфигурации, поэтому состав сборки подтверждён.

Перечень параметров. Определяется командой, поэтому значения видны.

Состав найденных зависимостей. Определяется журналом, поэтому отсутствие обнаруживается.

Состав целей. Определяется генератором, поэтому набор задан.

# Перечень параметров конфигурации
# meson configure build

# Повторная конфигурация после изменения описания
# meson setup build --reconfigure

# Подробный журнал конфигурации
# meson setup build --wipe -Denable_tests=true
def check_options(actual, expected):
    missing = {k: v for k, v in expected.items() if actual.get(k) != v}
    return missing

actual = {"enable_tests": True, "log_level": "info"}
print(check_options(actual, {"enable_tests": True, "log_level": "warn"}))

Проверка результатов сборки и их состав

Проверка выполняется после сборки, поэтому соответствие описанию подтверждено.

Состав собранных целей. Определяется перечнем файлов, поэтому набор виден.

Соответствие описанию. Сравнивается с перечнем целей, поэтому расхождение обнаруживается.

Состав установленных файлов. Определяется правилом установки, поэтому набор задан.

# Перечень собранных целей
# find build -type f -executable

# Состав устанавливаемых файлов
# meson install -C build --destdir /tmp/stage

# Проверка состава установки
# find /tmp/stage -type f
def check_targets(declared, built):
    missing = set(declared) - set(built)
    return sorted(missing)

print(check_targets({"service", "worker"}, {"service", "worker"}))
print(check_targets({"service", "worker"}, {"service"}))

Практическое следствие: сравнение перечня целей обнаруживает расхождение, поэтому описание соответствует фактической сборке.

Соответствие описания требованиям проекта

Соответствие определяется составом целей, поэтому описание согласовано с проектом.

Перечень целей. Определяет состав сборки, поэтому набор зафиксирован.

Версия языка. Определяет стандарт, поэтому совместимость задана.

Состав зависимостей. Определяет требования, поэтому набор задан явно.

project('service', 'cpp',
        version : '1.2.0',
        default_options : ['cpp_std=c++20', 'warning_level=2'])

fmt_dep = dependency('fmt', version : '>=10.0', required : true)
spdlog_dep = dependency('spdlog', version : '>=1.10', required : true)

executable('service',
           ['src/main.cpp', 'src/core.cpp'],
           include_directories : include_directories('include'),
           dependencies : [fmt_dep, spdlog_dep],
           install : true)

Практическое следствие: соответствие описания составу проекта исключает расхождение, поэтому цели заданы явно.

Ограничение состава сборки

Ограничение определяет состав целей, поэтому сборка согласована с описанием.

Перечень целей. Определяет набор сборки, поэтому лишнее исключено.

Параметры конфигурации. Определяют вариант, поэтому состав зафиксирован.

Состав зависимостей. Определяет требования, поэтому набор задан явно.

executable('service', ['src/main.cpp'], dependencies : [fmt_dep], install : true)
executable('worker', ['src/worker.cpp'], dependencies : [spdlog_dep])
# Перечень целей сборки
# ninja -C build -t targets all | head -n 10

Практическое следствие: ограничение состава сборки исключает лишние цели, поэтому описание согласовано.

Практика: работа с Meson

  1. Описание состава целей до сборки. Состав задан, поэтому правила генерируются автоматически.
  2. Явная обязательность зависимостей. Поведение при отсутствии задано, поэтому сборка завершается явно.
  3. Параметры для вариантов сборки. Значения заданы при конфигурации, поэтому описание не меняется.
  4. Описание в подкаталогах. Правка локализована, поэтому изменение состава не затрагивает корень.
  5. Объявление тестов описанием. Запуск выполняется одной командой, поэтому проверка унифицирована.
  6. Использование Ninja в качестве генератора. Правила генерируются, поэтому сборка выполняется быстро. О генераторе — в статье про Ninja.
  7. Кэш компиляции. Повторная сборка сокращается, поэтому время уменьшается. О кэше — в статье про кэш сборки C++.

Рекомендации. Описание определяет состав. Обязательность задает поведение. Параметры определяют вариант. Подкаталоги локализуют правку. Тесты запускаются единой командой.

Практический сценарий: добавление исходного файла не попадало в сборку

Диагностика. Новый исходный файл не попадал в сборку, а символ из него не находился при компоновке. Проверка показала, что состав источников задан явным списком.

Причина. Описание перечисляет исходные файлы явно, поэтому новый файл не участвовал в сборке до добавления в список.

Действия.

# Было: явный список без нового файла
# executable('service', 'src/main.cpp', dependencies : [fmt_dep])

# Стало: состав источников объявлен переменной
service_sources = files(
  'src/main.cpp',
  'src/core.cpp',
  'src/parser.cpp',
)

executable('service',
  service_sources,
  dependencies : [fmt_dep],
  install : true)
# Переконфигурация после изменения описания
# meson setup build --reconfigure
# ninja -C build

Результат. Новый файл участвует в сборке, поскольку состав источников объявлен переменной и дополняется без изменения объявления цели.

Вывод. Состав источников определяется описанием, поэтому добавление файла требует правки списка источников.

FAQ

Что определяет файл meson.build? Состав целей, зависимости и параметры проекта, поэтому правила сборки генерируются автоматически.

Как подключается необязательная зависимость? Параметром обязательности и условием, поэтому состав сборки определяется наличием пакета.

Зачем нужны параметры проекта? Они определяют вариант сборки, поэтому конфигурация выполняется без изменения описания.

Как запускаются тесты? Единой командой по описанию, поэтому проверка выполняется без отдельной команды запуска.

Источники: документация Meson, Meson: справочник, Meson: зависимости.