Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Meson - новая система сборки программного обеспечения

Оригинал: Meson - a new build system
Автор: Tim Schürmann
Дата публикации: 1 сентября 2014 г.
Перевод: А.Панин
Дата перевода: 18 апреля 2017 г.

Разработчики, уставшие от сложных Make-файлов, определенно должны обратить внимание на новую систему сборки программного обеспечения под названием Meson, которая проста в использовании, позволяет создавать сценарии сборки, поддерживает внешние инструменты для тестирования программного обеспечения и работает в Linux, Windows и Mac OS X.

Финский разработчик Jussi Pakkanen был удручен состоянием существующих систем сборки программного обеспечения, вынуждающих использовать глупые синтаксические конструкции в файлах конфигурации и зачастую ведущих себя непредсказуемо [1]. По этой причине он потратил рождественские праздники 2012 года на разработку собственной системы сборки программного обеспечения, которая должна была быстро работать, быть надежной и простой в использовании, поддерживать все популярные операционные системы и позволять осуществлять интеграцию таких важных инструментов для тестирования программного обеспечения, как Valgrind.

Спустя всего лишь два месяца Pakkanen опубликовал первую версию своей системы сборки программного обеспечения, которую он назвал, будучи опытным физиком, в честь частицы Мезон. С этого момента новая система начала быстрое развитие. Версия 0.17.0, то есть, новейшая версия на момент написания статьи, уже достаточно стабильна несмотря на малый номер и содержит реализации всех необходимых автору возможностей.

Meson позволяет осуществлять сборку исполняемых файлов и разделяемых библиотек и при этом поддерживает возможность создания множества директорий для генерации различных сборок программного обеспечения на основе одного и того же исходного кода. Гибкий язык конфигурации прост для изучения, открывает множество возможностей для разработчика и поддерживает условные инструкции if.

Основные возможности

Для разработки Meson использовался исключительно язык Python 3, причем сам код распространяется в соответствии с условиями лицензии Apache 2.0. Однако, один из незначительных недостатков Meson заключается в том, что в текущее время она может осуществлять сборку программного обеспечения на основе файлов исходного кода только на языках C, C++, Java и Vala. Meson не осуществляет передачу файлов исходного кода на вход компилятора; вместо этого она просто генерирует файлы конфигурации для существующих систем сборки программного обеспечения. В Linux Meson генерирует файлы конфигурации для относительно малоизвестной, но достаточно производительной минималистичной системы сборки программного обеспечения Ninja [2]. Также Meson может генерировать файлы проектов для VisualStudio 2010 и Xcode. В терминологии Meson упомянутые инструменты называются бэкэндами.

Если вам захочется воспользоваться данной системой сборки программного обеспечения в Linux, вам придется установить пакеты с компонентами Ninja 3 и Python с помощью менеджера пакетов программного обеспечения вашего дистрибутива. В Ubuntu для этой цели может использоваться команда

sudo apt-get install python3 ninja-build

с последующей загрузкой текущей версии Meson и извлечением ее компонентов из архива [3].

За установку в систему отвечает сценарий install_meson, который следует запускать от лица пользователя root. По умолчанию он копирует компоненты рассматриваемой системы сборки программного обеспечения в директорию /usr/local. Однако, параметр --prefix <директория> позволяет выбрать отличную директорию для установки.

Вместо установки в систему пользователи могут вызывать Meson непосредственно из любой директории. В этом случае короткая команда meson должна заменяться на <директория>/meson.py.

План сборки

Далее вам нужно будет создать небольшой файл конфигурации под названием meson.build в директории с исходным кодом вашего проекта. Пример содержимого данного файла приведен в Листинге 1. В файле meson.build должен использоваться собственный язык программирования Meson; этот язык должен быть понятен программистам, имеющим опыт работы с языком Python. В Meson производится не совсем очевидная отсылка к функциям project() и executable(), как к командам. Каждая инструкция должна размещаться в отдельной строке.

Листинг 1: Пример содержимого файла meson.build

project('Hello World', 'c')  # Название проекта
src = ['main.c', 'file1.c', 'file2.c']
executable('hello', sources : src)

Начало работы над проектом

Новый проект объявляется с помощью ключевого слова или функции project(); данная функция ожидает два аргумента: имя проекта и название используемого языка программирования. В Листинге 1 проект носит имя Hello World; его исходный код написан на языке программирования C. Вооружившись данной информацией, Meson может выбрать подходящий для сборки проекта компилятор. Программисты на языке C++ должны использовать строку cpp в качестве второго параметра функции. Строки должны помещаться в одинарные кавычки, причем символ # позволяет рассматривать строку в качестве комментария; Meson игнорирует любые инструкции, находящиеся после символа решетки, до перехода на следующую строку.

Во второй строке Листинга 1 описывается переменная с именем src. В качестве значения этой переменной устанавливается массив с именами файлов исходного кода проекта. Квадратные кавычки позволяют объявить массив. В дополнение к массивам, в качестве значений переменных могут использоваться целочисленные значения (num = 123), логические значения (right = true) и строковые значения (name = 'Peter'). Вы не должны явно указывать типы этих значений (используется динамическая типизация); однако, вы не можете осуществлять преобразования типов данных. Наконец, разработчик должен всегда описывать в файле meson.build цель сборки. Она определяет, что должно генерироваться с помощью Meson: разделяемая библиотека, исполняемый файл или и то и другое.

В Листинге 1 функция executable() гарантирует генерацию исполняемого файла с помощью компилятора. По этой причине ей передаются имя программы и список компилируемых файлов.

Функция executable() также позволяет использовать имена параметров перед их значениями для улучшения читаемости файла конфигурации. В Листинге 1 эта возможность была использована при объявлении параметра sources : src. Подобные синтаксические конструкции называются ключевыми аргументами или именованными параметрами в других языках программирования.

Двумя другими целями сборки помимо execute() являются static_library() и shared_library(), позволяющие генерировать статические и разделяемые библиотеки соответственно. Следующий вызов позволяет собрать версию 1.2.3 разделяемой библиотеки с именем libhello на основе файлов исходного кода lib1.c и lib2.c:

shared_library('hello', ['lib1.c', 'lib2.c'],version : '1.2.3')

Указание версии библиотеки является необязательным. Функция static_library() вызывается аналогичным образом, но не позволяет указать версию библиотеки. Разработчики могут объявлять несколько различных целей сборки одновременно; в результате Meson будет последовательно обрабатывать их.

Три строки из Листинга 1 - это все, что нужно Meson. Инструмент сам выберет подходящий компилятор и подберет необходимые параметры сборки.

Разделяй и властвуй

Для компиляции вашего исходного кода вам в первую очередь потребуется создать новую поддиректорию для хранения всех сгенерированных компилятором файлов. Эта сборочная директория является необходимой: Meson предполагает обязательное использование отдельной директории и не может быть настроен для размещения файлов объектного кода в директории с файлами исходного кода.

Такой подход имеет несколько преимуществ: во-первых, он позволяет хранить генерируемые компилятором файлы отдельно от файлов исходного кода, что упрощает понимание структуры проекта, особенно в случае сложных проектов, а также гарантирует то, что файлы с объектным кодом не будут обрабатываться системой контроля версий. С другой стороны, вы можете создать сразу несколько независимых сборок проекта с различными конфигурациями. Например, одна сборочная директория может содержать стабильную версию, а другая - разрабатываемую.

Имена директорий для сборки проекта, а также их расположение зависят от вас. В большинстве случаев директория сборки размещается в основной директории с исходным кодом проекта и носит стандартное имя build:

cd helloworld/src
mkdir build

Разработчику придется позволить Meson заполнить необходимыми для сборки проекта файлами новую сборочную директорию (также обратите внимание на Рисунок 1):

meson ~/helloworld/src ~/helloworld/src/build

Если вы вызовете Meson из директории сборки так, как показано на данной иллюстрации, вам придется лишь указать путь к директории с исходным кодом

Рисунок 1: Если вы вызовете Meson из директории сборки так, как показано на данной иллюстрации, вам придется лишь указать путь к директории с исходным кодом.

Первым параметром является путь к директории с исходным кодом, в которой также должен быть размещен файл meson.build. Вторым параметром является путь к директории сборки. В этой директории Meson создаст все необходимые файлы конфигурации или файлы сборки для Ninja. Из нее разработчик сможет инициировать сборку самой программы:

cd build
ninja

Ninja автоматически задействует все доступные ядра центрального процессора. Если вам понадобится пересобрать вашу программу впоследствии, вам достаточно будет снова вызвать утилиту ninja из директории build. Дополнительных команд не потребуется. Силами Meson уже была осуществлена настройка Ninja, поэтому данная система сборки программного обеспечения будет автоматически выявлять изменения файлов исходного кода проекта и перекомпилировать лишь эти файлы - даже после изменения файла meson.build.

По умолчанию компилятор добавляет отладочную информацию в результирующие бинарные файлы и выводит все предупреждения. В случае использования компилятора GCC Meson активирует параметры -g и -Wall. Если вам нужна оптимизированная версия программы для публикации на сайте, вы можете вызвать Meson с параметром --buildtype=release. Дополнительная информация об управлении процессом компиляции доступна в разделе "Полный контроль".

Полный контроль

Meson самостоятельно принимает решение о том, какой компилятор и с какими параметрами следует вызвать. Однако, разработчикам иногда нужно контролировать процесс сборки программного обеспечения - например, для сборки пакета в соответствии с требованиями определенного дистрибутива. И снова это не будет связано с какими-либо проблемами. Вы можете указать, какой компилятор следует использовать, с помощью переменных окружения. Например, это команда, позволяющая использовать компилятор Clang вместо GCC:

CC=clang meson ~/helloworld/src ~/helloworld/src/build

Вы даже можете задать или изменить стандартные параметры компилятора, установив значения соответствующих переменных окружения перед вызовом meson. Параметр --buildtype=plain также позволяет отказаться от использования стандартных параметров компилятора, выбранных Meson:

CFLAGS="-o2" meson --buildtype=plain ~/helloworld/src ~/helloworld/build

Если вы хотите установить скомпилированную программу с помощью Ninja, вы можете указать целевую директорию для установки с помощью переменной окружения DESTDIR:

DESTDIR=/opt/helloworld ninja install

Альтернативным решением является простое использование отличного префикса пути установки: ninja install --prefix /usr.

В Linux Meson всегда использует Ninja в качестве бэкэнда. Проект для Visual Studio 2010 создается с помощью параметра --backend=vs2010; Meson генерирует эквивалентный проект для Xcode в случае использования параметра --backend=xcode. Также Ninja может устанавливать скомпилированную программу непосредственно в систему. Для этого нужно просто выполнить команду ninja install.

Объект желания

Язык программирования, используемый в файлах meson.build системы сборки программного обеспечения Meson, является объектно-ориентированным. Хотя использующие это язык разработчики и не могут самостоятельно реализовывать классы, некоторые его функции возвращают объекты. Например, функция shared_library() возвращает объект, который представляет цель сборки. Данный объект может быть сохранен в переменной:

lib = static_library('hello', ['lib1.c','lib2.c'])

Впоследствии вы сможете передать этот объект в качестве аргумента другой функции, к примеру, executable(), которая позволит осуществить связывание бинарного файла программы непосредственно с созданной ранее библиотекой:

executable('hello', 'main.c', link_with : lib)

Если вы используете в своей программе функции из файла исходного кода lib2.c, вам наверняка захочется проверить корректность их работы с помощью тестирующей программы. Вам не придется связывать ее со всей динамической библиотекой - достаточно будет связать ее с файлом объектного кода, сгенерированным на основе файла исходного кода lib2.c.

К счастью, объект, возвращаемый функцией shared_library(), имеет метод extract_objects, который, в свою очередь, представляет файл объектного кода. Вам придется вызвать этот метод, воспользовавшись точечной нотацией таким же образом, как во многих объектно-ориентированных языках программирования:

obj = lib.extract_objects('lib2.c')

Теперь полученный файл объектного кода может быть связан с программой:

executable('hello', 'main.c', objects : obj)

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

Обход дерева

В том случае, если файлы исходного кода проекта хранятся в поддиректориях, функция subdir может использоваться для перехода в одну из них - в данном случае, в поддиректорию gui:

subdir('gui')

В результате Meson будет искать файл конфигурации meson.build в данной поддиректории. Однако, в этом файле не должна использоваться функция project, так как описание проекта уже размещено в родительском файле конфигурации meson.build. Если заголовочные файлы проектов на языках C и C++ размещены в поддиректории include, вы можете добавить путь к данной поддиректории в список путей для поиска заголовочных файлов компилятора аналогичным образом:

header = include_directories('include')
executable('hello', 'main.c',include_dirs : header)

Большая часть крупных проектов использует дополнительные библиотеки, такие, как компрессор zlib или графический тулкит GTK. Для подключения к проекту каждой из таких библиотек достаточно всего двух строк:

gtk = dependency('gtk+-3.0')
executable('hello', 'main.c', deps : gtk)

Функция dependency() в первую очередь проверяет наличие копии указанной библиотеки в системе. В случае ее отсутствия процесс сборки проекта прерывается; в противном случае Meson связывает эту библиотеку с программой hello. Этот автоматизированный механизм работает со всеми библиотеками, в комплекте поставки которых находятся файлы pkg-config. К счастью, Meson может без какого-либо посредничества работать с некоторыми довольно популярными фреймворками, не снабженными данными файлами. На текущий момент такими фреймворками являются Boost, Qt5, Google Test (gtest) и Google Mock (gmock). В случае с Boost вы можете даже выбрать необходимые модули:

boost = dependency('boost', modules : ['thread', 'utility'])

Meson поддерживает условные инструкции if, позволяющие реализовывать более сложные проверки. В Листинге 2 показан пример: в проекте на языке C осуществляется проверка наличия в системе заголовочного файла sys/fstat.h. Для этого Meson обращается к компилятору в первой строке. Во второй строке компилятор отвечает на вопрос о том, присутствует ли в системе необходимый заголовочный файл (Рисунок 2).

Листинг 2: Условное ветвление

compiler = meson.get_compiler('c')
if compiler.has_header('sys/fstat.h')
         # Заголовочный файл существует
else:
         # Заголовочный файл не существует
endif

Ninja выводит информацию о результате проверки позднее. В данном случае из-за отсутствия заголовочного файла выводится сообщение об ошибке и прерывается процесс сборки программы no_hello

Рисунок 2: Ninja выводит информацию о результате проверки позднее. В данном случае из-за отсутствия заголовочного файла выводится сообщение об ошибке и прерывается процесс сборки программы no_hello.

Тестирование

Meson позволяет использовать простые модульные тесты, формат описания которых продемонстрирован в Листинге 3. В данном случае функция test() позволяет объявить новый тест под названием "A test". Данный тест заключается в запуске программы hello и передаче ей параметров --debug и --encode. Перед этим Meson устанавливает значение EN_us переменной окружения HELLOLANG и значение /opt/helloworld переменной окружения HELLODIR. Если программа hello завершается с кодом 0, тест считается пройденным; любой другой код завершения работы программы свидетельствует о неудачном завершении тестирования.

Листинг 3: Модульный тест

prg = executable('hello', 'main.c')
test('A Test', prg, args : ['--debug', '--encode'], env : ['HELLOLANG=EN_us', 'HELLODIR=/opt/helloworld'])

Для выполнения теста разработчик должен скомпилировать программу с помощью ninja обычным образом, после чего воспользоваться командой ninja test (Рисунок 3). Вывод теста сохраняется в файле meson-logs/testlog.txt. Если разработчик объявил несколько тестов, по умолчанию Meson будет выполнять их в параллельном режиме. Если вы хотите изменить данное поведение, вы должны вызвать функцию test аналогичным образом:

test('A Test', prg, is_parallel : false)

Ninja может выполнять тестирование программного обеспечения в автоматическом режиме. В данном случае программа успешно прошла тест под названием A Test

Рисунок 3: Ninja может выполнять тестирование программного обеспечения в автоматическом режиме. В данном случае программа успешно прошла тест под названием A Test.

Дополнительно Meson может добавлять в проект поддержку инструментов для отладки и тестирования. Например, если при вызове meson вы передадите параметр --enable-gcov, будет осуществлена проверка наличия в системе инструмента для тестирования покрытия кода Gcovr. В случае наличия данного инструмента в системе будет подготовлена новая цель сборки coverage-xml:

ninja coverage-xml

При передаче данного параметра Meson автоматически использует Gcovr для проверки кода программы; его вывод будет сохранен в файле coverage.xml из директории meson-logs/. Таким же образом вы можете добавить в свой проект поддержку таких инструментов, как Valgrind и Cppcheck [4].

Дополнительные параметры

Meson предоставляет множество других возможностей. Например, данный инструмент генерирует корректные файлы pkg-config при необходимости; он поддерживает механизм локализации приложений на основе Gettext и позволяет использовать кросскомпиляторы. Перед компиляцией он может генерировать необходимые для сборки проекта и описанные разработчиком файлы конфигурации, такие, как широко известный заголовочный файл config.h. Встроенная в Meson система шаблонов помогает в этом; например, она позволяет автоматически заменять шаблон @version@ в строке #define VERSION "@version@" на соответствующую строку (1.2.3) из файла meson.build [5]. Вы также можете объявлять абсолютно новые цели сборки [6]. Активация и использование описанных возможностей обычно связаны с добавлением нескольких строк в файл конфигурации meson.build.

Очень подробное и понятное руководство по работе с Meson опубликовано на ресурсе SourceForge [7]. Ответы на наиболее часто задаваемые вопросы можно найти в разделе FAQ [8] и в официальном списке рассылки [9]. Пока Meson не поддерживает IDE, но система сборки программного обеспечения предоставляет API, который может использоваться любым разработчиком для интеграции Meson в его любимую интегрированную среду разработки [10]. Простой графический интерфейс находится на начальном этапе разработки (Рисунок 4). Если вы хотите ускорить скорость сборки программных продуктов, обратите внимание на советы из раздела "Ускорение сборки".

В комплект поставки Meson включен графический интерфейс под названием mesongui; в текущее время он позволяет изменить значения лишь нескольких параметров конфигурации системы сборки программного обеспечения с помощью мыши

Рисунок 4: В комплект поставки Meson включен графический интерфейс под названием mesongui; в текущее время он позволяет изменить значения лишь нескольких параметров конфигурации системы сборки программного обеспечения с помощью мыши.

Ускорение сборки

Разбор заголовочных файлов системных библиотек может занимать достаточно много времени. Скомпилированные заголовочные файлы могут помочь в данном случае. В результате компилятор будет однократно разбирать заголовочные файлы и сохранять внутреннее данные состояния в файл на жестком диске. При следующей сборке компилятор просто извлечет данные состояния из этого файла.

Для использования скомпилированных заголовочных файлов в Meson в первую очередь нужно разместить все директивы #include в новом заголовочном файле - назовем его hello_pch.h. Этот файл должен быть сохранен новой поддиректории с именем pch. Этот заголовочный файл с именем hello_pch.h не должен подключаться ни к одному из файлов исходного кода, а путь к поддиректории pch не должен находиться в списке путей заголовочных файлов компилятора. Если эти условия выполняются, вы можете просто дополнить вызов функции executable() параметром c_pch:

executable('hello', sources: src, c_pch : 'pch/hello_pch.h')

Для дополнительной оптимизации времени сборки Meson позволяет использовать механизм объединенных сборок (unity builds) [11]. Этот механизм позволяет объединить содержимое файлов исходного кода проекта в рамках одного большого файла и передать этот файл компилятору. Эта мера позволяет уменьшить время компиляции проекта до 50 процентов в зависимости от его особенностей.

Однако, в случае модификации одного из этих файлов компилятору придется повторно скомпилировать весь исходный код проекта. По этой причине механизм объединенных сборок Meson отключен по умолчанию. Для его активации необходимо передать параметр --unity при вызове meson. Остальная часть работы будет выполняться системой сборки программного обеспечения; вам не придется объединять файлы исходного кода вручную.

Заключение

Хотя система сборки программного обеспечения Meson является еще очень молодой, она на удивление стабильна и отвечает требованиям разработчика. В частности, она позволяет сократить объем работы, который должен быть выполнен разработчиком программного обеспечения: если вы работали с инструментами automake ранее, вы наверняка не захотите отказываться от Meson.

Текущая версия Meson уже пригодна для промышленной эксплуатации и отлично подходит для проектов среднего размера. Однако, Jussi Pakkanen обращает внимание на то, что текущая реализация все еще является рабочим прототипом и может измениться в процессе последующей разработки. Также автор заявил, что в скором времени Meson появится в официальном репозитории дистрибутива Debian.

Источники дополнительной информации

  1. Описание архитектуры Meson: http://sourceforge.net/p/meson/wiki/Design%20rationale/
  2. Ninja: http://martine.github.io/ninja/
  3. Meson: http://sourceforge.net/projects/meson/
  4. Функции автоопределения: http://sourceforge.net/p/meson/wiki/Feature%20autodetection/
  5. Конфигурация: http://sourceforge.net/p/meson/wiki/Configuration/
  6. Дополнительные цели сборки: http://sourceforge.net/p/meson/wiki/Custom%20build%20targets/
  7. Руководство по использованию Meson: http://sourceforge.net/p/meson/wiki/Manual/
  8. Meson FAQ: http://sourceforge.net/p/meson/wiki/FAQ/
  9. Список рассылки: https://lists.sourceforge.net/lists/listinfo/meson-devel
  10. Интеграция с IDE: http://sourceforge.net/p/meson/wiki/IDE%20integration/
  11. Механизм объединенных сборок: http://sourceforge.net/p/meson/wiki/Unity%20builds/