Компилирование приложений в GNU/Linux
#
Васильев Андрей Михайлович, 2023
Версии презентации
Минимальное приложение
#
Рассмотрим нативное приложение под операционную систему, main.c
:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
Скомпилируем данное приложение:
Удостоверимся, что приложение работает
Сборка разных типов приложений
#
- Часть языков программирования требуют шага компиляции при работе: Си, C++, Rust, Go, Java, Kotlin, C#, Zig, Nim и т.д
- Часть языков программирования требуют упаковки исходных кодов, интерпретируемые языки программирования: Python, JavaScript/Node
- Современные приложения обычно состоят из нескольких компонентов, написанных на нескольких языках программирования, которые надо объединить
Сборка и установка приложений
#
В общем процесс сборки приложения можно «втиснуть» в следующую схему:
- Получение исходных кодов приложения
- Выяснение и установка зависимостей для сборки приложения
- Выполнение сборки приложения (шаг может быть пропущен для интерпретируемых языков программирования)
Процесс установки собранного приложения «вписываются» в следующую схему:
- Установка зависимостей для запуска приложения
- Размещение скомпилированных файлов приложения по целевым путям
- Исполняемые файлы в PATH-каталоги, например в
/usr/local/bin
- Файлы ресурсов приложения например в
/usr/local/share
Обе задачи можно решить с помощью систем сборок
Системы сборок
#
- Упрощают работу с большим количеством исходных кодов, обеспечивают данное решение
- Позволяют автоматизировать задачи разработки и тестирования
- Сборка приложения из исходных кодов
- Запуск автоматических тестов после сборки
- Создание документации по исходным кодам
- Публикация артефактов сборки на сайтах или магазинах
- Позволяют выполнять задачи на автоматизированных системах непрерывной интеграции
Современные кодовые базы помимо исходных кодов приложения содержат также и конфигурацию системы сборки, которой пользуются разработчики
Система сборки Make
#
Классический инструмент сборки — Make, созданный в 74 году
Основные концепции Make:
- Результат работы системы сборки — файл
- Результаты получаются из исходных файлов
- Если время изменения исходных файлов позже времени изменения результата, значит нужно выполнять сборку
- Если время изменения файла-результата позже времени изменения исходных файлов, значит сборку выполнять не нужно
- Результаты могут зависеть от других результатов
flowchart LR
source_1[Исходный файл № 1]
source_2[Исходный файл № 2]
source_3[Исходный файл № 3]
intermediate_1[Промежуточный результат № 1]
intermediate_2[Промежуточный результат № 2]
final[Результат]
source_1 --> intermediate_1
source_2 --> intermediate_1
source_3 --> intermediate_2
intermediate_1 --> final
intermediate_2 --> final
Синтаксис Makefile
#
Makefile — конфигурационный файл для приложения Мake
цель: зависимости ...
команды по сборке
- Цель — путь к файлу-результату, который должен получиться
- Зависимости — пути к файлам исходных кодов для цели
- Команды по сборке — SH-команды, которые создают цель. Должны быть отделены символом табуляции
Компиляция приложения
#
Сформируем Makefile для компиляции приложения
app: main.c
cc -o app main.c
Запустим сборку приложения с помощью Мake:
Больше возможностей Make
#
Рассмотрим следующий Makefile
all: app
app: main.o
gcc -o app main
main.o: main.c
gcc -c main.c
clean:
rm hello.o hello.exe
- Процесс компиляции приложения состоит из фаз создания объектных файлов с дальнейшей их линковкой
all
и clean
предоставляет из себя фальшивые цели, они не создают файлы
- При запуске
make
без аргументов будет выполнена первая фальшивая цель
- Данный Makefile автоматизирует как создание файлов, так и их удаление
Другие возможности Make
#
- Переменные позволяют сформировать список исходных кодов только один раз
- Автоматические переменные позволяют упросить написание SH-правил для выполнения действий
- Шаблонные правила позволяют описать правила по преобразованию файлов одних типов файлов в другие
- Встроенные шаблонные правила позволяют писать ещё меньше правил
Make остаётся достаточно эффективным средством сборки приложений и автоматизации других действий, основанный на файлах
На настоящий момент на большинстве ОС используется реализация Make от проекта GNU, GNU Make
Сложности современного мира
#
При разработке приложения разработчику необходимо прибегать к использованию библиотек и особенностей, которые доступны только на конкретной платформе:
- Как называется компилятор языка Си/C++
- Как правильно передавать ему необходимость использования нужной версии стандарта языка и другие аргументы
- Находится ли в системе нужная библиотека
- Как правильно подключать заголовочные файлы библиотеки и библиотечные файлы
В рамках логической структуры Make реализовать работу данных задач удобным образом достаточно сложно
Эти проблемы были актуальны уже для приложений, разрабатывающихся в конце 80-х годов (множество UNIX-подобных систем)
Генераторы Makefile
#
Для решения задачи адаптации сборочного процесса к компьютеру были придуманы более высокоуровневые инструменты, которые можно назвать генераторами Makefile:
flowchart LR
gen_config[[Конфигурационный\nфайл генератора]]
generator(Генератор\nMakefile)
makefile[[Makefile]]
gen_config --> generator
generator --> makefile
Конфигурационные файлы генератора обычно являются тьюринг-полными языками программирования, предоставляющие удобные инструменты для решения задачи
Можно выделить следующие генераторы: GNU Autotools, CMake, Meson, SCons, QMake
Альтернативы
#
- Существуют другие инструменты сборки, например Ninja, Ant
- Существуют интегрированные решения, выполняющие сборку приложений целиком, например Bazel, Gradle, build2 и т.п.
- Можно определить наличие системы сборки по файлам
configure
, configure.ac
, aclocal.m4
и т.д.
- Пользователю для сборки приложения достаточно:
- Создать Makefile:
./configure
(надо будет поставить все зависимости)
- Запустить сборку:
make
(или быстрее с make -j
)
- Выполнить установку:
su -c 'make install'
- Разработчику предоставляются средства для указания:
- Списка зависимостей приложения
- Списка устанавливаемых файлов
- Списка требований к компилятору
- По умолчанию приложение будет установлено в каталог
/usr/local
, что можно изменить, указав префикс: ./configure --prefix ~/my-apps/
- При установке в домашний каталог не надо повышать привилегии для установки приложения
Работа CMake
#
flowchart TB
cmake_config[CMakeLists.txt]
cmake(cmake)
makefile[Makefile]
make(make)
executable[application]
cmake_config --> cmake
cmake --> makefile
makefile --> make
make --> executable
- Конфигурационный файл называется
CMakeLists.txt
- Для создания конфигурационного файла Make надо запустить
cmake
в каталоге вместе с CMakeLists.txt
- Затем выполнить компиляцию и установку приложения:
make && su -c 'make install'
В новых версиях CMake команды по сборке и установке можно вызвать через CMake, также рекомендуется создать отдельный каталог для сборки
mkdir build
cd build
cmake ../
cmake --build .
cmake --install . --prefix ~/my-apps
Установка зависимостей приложений
#
При выполнении этапа подготовки сборки система конфигурирования может сообщить нам о том, что одна из библиотек не была найдена:
checking for libpcre... no
configure: error: Package requirements (libpcre) were not met:
No package 'libpcre' found
Для компиляции приложения необходимо, чтобы в системе были установлены:
- Бинарные файлы библиотек (необходимы и для дальнейшей работы)
- Заголовочные файлы библиотек и подключаемые компилируемые компоненты
Заголовочные файлы поставляются в дистрибутивах отдельно от файлов библиотек
- В AltLinux такие пакеты обычно имеют префикс
lib
и суффикс -devel
- В Debian-подобных дистрибутивах имеют префикс
lib
и суффикс -dev
Альтернативные варианты ошибок
#
Не найден заголовочный файл
#
Можно воспользоваться поиском по содержимому пакетов с помощью apf
или epm
Не найдена библиотека в pkg-config
#
pkg-config — инструментарий для помощи при компиляции приложения
- Позволяет разработчику-клиенту получить список аргументов к компилятору для подключения библиотеки
- Позволяет разработчику библиотеки образом указать эти параметры
- Конфигурационные файлы данного инструмента имеют расширение
.pc
- Большинство систем сборок имеют удобные средства интеграции с pkg-config
- Можно легко поставить нужный пакет с использованием epm:
epm install pkgconfig(history)
Окружение для сборки
#
Во время сборки приложений происходит вызов множества приложений:
- Приложение для генерации конфигурации системы сборки
- Система сборки
- Инструментарий для управления ключами сборки (например для подключения зависимостей pkg-config)
- Дополнительные инструменты, используемые во время сборки приложения
Во время компиляции также необходимо обеспечить наличие библиотек, которые нужны приложению
Окружение для запуска
#
В окружении для запуска нужно предоставить только приложение и библиотеки, от которых оно зависит
Массовая установка приложений
#
Установка приложений из исходных кодов интересна:
- Разработчику приложения для проведения тестирования его работы в конкретном дистрибутиве
- Пользователю, если приложение не доступно в репозитории дистрибутива
- Пользователю, желающему получить более свежую версию ПО чем доступна в репозитории дистрибутива
- Пользователю, желающему получить предыдущую версию ПО, недоступную из репозитория
Если установку приложения после компиляции предполагается выполнить на множестве компьютеров, то логично создать пакет для установки с помощью пакетного менеджера
Если пакет потребовался Вам, то он может быть интересен и другим. Можно предоставить пакет для всех пользователей дистрибутива, приняв участие в общем проекте
Создание RPM-пакетов
#
flowchart LR
subgraph Пакет с исходными кодами
sources[Исходные коды приложения]
spec[Спецификация сборки]
end
rpmbuild(rpmbuild)
rpm-file[RPM-архив приложения]
sources --> rpmbuild
spec --> rpmbuild
rpmbuild --> rpm-file
Для выполнения сборки RPM-пакетов используется приложение rpmbuild
- Устанавливает все необходимые зависимости для компиляции приложения
- Собирает (компилирует) приложение в финальную форму
- Формирует список зависимостей, которые необходимы для работы приложения
- Собирает файлы и мета-информацию в один или несколько RPM-архивов
Локальная сборка RMP-пакета в ALT Linux
#
Рассмотрим процесс создания пакета вручную, для этих целей используются
- Gear — система для создания RPM-пакетов из git-репозитория
- Hasher — инструмент для безопасной и воспроизводимой сборки пакетов
- rpmbuild — низкоуровневый инструмент для выполнения сборки
- rpmlint — инструмент базовой проверки ошибок при создании пакетов
- patch — инструмент для внесения изменений в исходный код на основе файлов
- rpmdevtools — набор инструментов, упрощающих сборку RPM-пакетов
- buildreq — инструмент для поиска сборочных зависимостей
apt-get update
apt-get install gcc rpm-build rpmlint make python gear hasher patch \
rpmdevtools
Дистрибутивы отличаются друг от друга выбираемыми инструментами и особенностями их работы
Рабочее пространство для сборки RPM-пакетов
#
Чтобы создать дерево каталогов, которое является рабочей областью сборки RPM-пакетов, используйте приложение rpmdev-setuptree, или же создайте каталоги вручную, используя приложение mkdir:
$ rpmdev-setuptree
$ tree ~/rpmbuild/
/home/user/rpmbuild/
|-- BUILD
|-- RPMS
|-- SOURCES
|-- SPECS
-- SRPMS
- BUILD: Содержит все файлы, которые появляются при сборке пакета.
- RPMS: Здесь формируются собранные RPM-пакеты (.rpm) в подкаталогах для разных архитектур, например, в подкаталогахx86_64 и noarch.
- SOURCES: Здесь находятся архивы исходного кода и патчи. Утилита rpmbuild ищет их здесь.
- SPECS: Здесь хранятся spec-файлы.
- SRPMS: Здесь находятся пакеты с исходниками (.src.rpm).
Настройка общих параметров сборки
#
В домашнем каталоге пользователя находится файл .rpmmacros
, в котором указаны:
- Местоположение каталогов для сборки
- Указание ключа для подписывания пакетов
- Информация о том, кто выполнял сборку пакета
%_topdir %homedir/RPM
#%_tmppath %homedir/tmp
# %packager Joe Hacker <joe@email.address>
# %_gpg_name joe@email.address
Данный файл содержит определения RPM-макросов. Макросы начинаются со знака процента, затем идёт название макроса, а его значение записывается через пустую строку
Файлы, начинающиеся со знака #
являются комментариями
- Необходимо заполнить значение макроса
%packager
- Удалить определение макроса
%__arch_install_post
, если присутствует
Source RPM, .src.rpm файлы
#
.src.rpm
-файлы являются пакетами с исходными кодами приложения, из которых получаются RPM-пакеты после сборки
Рассмотрим src.rpm-пакет текстового редактора nano:
$ rpm -qlp nano-7.2-alt1.1.src.rpm
nano-7.2-build.patch
nano-7.2.tar
nano.spec
nano.spec
— файл спецификации по сборке приложения
nano-7.2.tar
— исходные коды приложения, полученные от разработчика
nano-7.2-build.path
— набор изменений в исходные коды от мейнтейнера
Данные файлы можно получить
Сборка из Source RPM
#
- Установите инструменты сборки
- Настройте окружение для выполнения сборки
- Получите .src.rpm-файл для сборки
- Распакуйте файл в сборочный каталог
rpm -i FILE.src.rpm
(выполнять с правами обычного пользователя)
$ tree ~/RPM
~/RPM
├── SOURCES
│ └── nano
│ ├── nano-7.2-build.patch
│ └── nano-7.2.tar
└── SPECS
└── nano.spec
- Выполните сборку приложения
rpmbuild -ba RPM/SPECS/nano.spec
- Сборка может не пройти ввиду отсутствующих зависимостей
- Собранные RPM-пакеты будут находится в
~/RPM/RPMS/
Spec-файлы
#
Spec-файл можно рассматривать как “инструкцию”, которую утилита rpmbuild использует для фактической сборки RPM-пакет. Он сообщает системе сборки, что делать, определяя инструкции в серии разделов.
Разделы определены в Преамбуле и в Основной части. Преамбула содержит ряд элементов метаданных, которые используются в Основной части. Тело содержит основную часть инструкций.
Пример Spec-файла можно посмотреть в AltLinux Wiki
Пункты преамбулы
#
- Name — Базовое имя пакета, которое должно совпадать с именем Spec-файла.
- Version — Версия upstream-кода.
- Release — Релиз пакета используется для указания номера сборки пакета при данной версии upstream-кода. Как правило, установите начальное alt1 и увеличивайте его с каждым новым выпуском пакета, например: alt1, alt2, alt3 и т.д.
- Summary — Краткое, в одну строку, описание пакета.
- License — Лицензия на собираемое программное обеспечение.
- Source0 — Путь или URL-адрес к сжатому архиву исходного кода (не исправленный, исправления обрабатываются в другом месте). При необходимости можно добавить дополнительные исходные директивы, каждый раз увеличивая их количество, например: Source1, Source2, Source3 и так далее.
- Patch0 — Название первого патча, который при необходимости будет применен к исходному коду. При необходимости можно добавить дополнительные директивы PatchX, увеличивая их количество каждый раз, например: Patch1, Patch2, Patch3 и так далее.
- BuildRequires — Разделённый запятыми или пробелами список пакетов, необходимых для сборки программы. Может быть несколько записей BuildRequires, каждая в отдельной строке в SPEC файле.
- Requires — Разделённый запятыми или пробелами список пакетов, необходимых программному обеспечению для запуска после установки. Это его зависимости Может быть несколько записей Requires, каждая в отдельной строке в Spec-файле.
Составляющие основной части
#
Основная часть состоит из ряда разделов. Каждый раздел обозначается заголовком
- %description — Полное описание программного обеспечения, входящего в комплект поставки RPM. Это описание может занимать несколько строк.
- %prep — Команда или серия команд для подготовки программного обеспечения к сборке, например, распаковка архива из Source0. Эта директива может содержать сценарий оболочки (shell скрипт).
- %build — Команда или серия команд для фактической сборки программного обеспечения в машинный код (для скомпилированных языков) или байт-код (для некоторых интерпретируемых языков).
- %install — Раздел, который во время сборки пакета эмулирует конечные пути установки файлов в систему. Команда или серия команд для копирования требуемых артефактов сборки из %builddir (где происходит сборка) в %buildroot каталог (который содержит структуру каталогов с файлами, подлежащими сборке). Обычно это означает копирование файлов из ~/rpmbuild/BUILD в ~/rpmbuild/BUILDROOT и создание необходимых каталогов ~/rpmbuild/BUILDROOT. Это выполняется только при создании пакета, а не при установке пакета конечным пользователем.
- %check — Команда или серия команд для тестирования программного обеспечения. Обычно включает в себя такие вещи, как запуск модульных тестов.
- %files — Список файлов, которые будут установлены в системе конечного пользователя.
- %changelog — Запись изменений, произошедших с пакетом между различными Version или Release сборками.
RPM-макросы
#
Макросы RPM — это прямые текстовые подстановки, которые происходят путем замены определенных выражений и условий на соответствующий текст во время процесса сборки пакета. Имена макросов начинаются с символа %
- Обеспечивают нужную функциональность
- Облегчает задачи мейнтенеров пакетов, уменьшая дублирование
- Делает Spec-файлы более понятными (с какого-то момента)
Просмотреть список доступных макросов и их значения можно, выполнив команду:
Получить значение, раскрываемое макросом можно, использовав команду rpm --eval <имя_макроса>
.
$ rpm --eval %_sysconfdir
/etc
Вопросы массовой сборки приложений
#
- Откуда появляется пакет с исходными кодами?
- Кто формирует спецификацию?
- Что делать, когда выходит новая версия приложения?
- Что делать, когда выходит новая версия библиотеки, от которой зависит приложение?
- Как точно удостоверится, что все зависимости для сборки точно указаны в зависимостях?
- Как выполнить сборку приложения под все поддерживаемые архитектуры?
- Как организовать публикацию RPM-пакетов в репозиторий?
Разработчики дистрибутивов разрабатывают системы, позволяющие решать эти задачи. Даже при использовании одинаковых инструментов конкретные настройки могут отличаться
Инструменты Alt Linux
#
girar, gigrar-builder — инструменты для автоматизированной сборки пакетов Sisyphus
- Управление git-репозиториями (создание, удаление, обновление, перемещение, копирование, поиск по имени репозитория)
- Управление системой оповещений об изменениях в git-репозиториях
- Управление правами доступа к сборке пакетов в Sisyphus и другие бранчи
- Управление сборкой пакетов в Sisyphus и другие бранчи из gear-репозиториев и SRPM-пакетов (сборка пакетов реализована системой girar_builder)
- Управление аккаунтами пользователей git.alt
Принципиальная архитектура girar
#
flowchart TB
subgraph Хостинг Git-репозиториев
repo_one["Репозиторий пакета"]
repo_two["Репзоиторий пакета"]
end
control(Управляющий сервер\ngirar)
subgraph builder_arm[Сборочный сервер\nARM]
gear_one[gear]
hasher_one[hasher]
end
repository_arm[[Репозиторий ARM]]
subgraph builder_x86_64[Сборочный сервер\nx86_64]
gear_two[gear]
hasher_two[hasher]
end
repository_x86_64[[Репозиторий x86_64]]
control -.-> builder_arm
control -.-> builder_x86_64
repo_one --> builder_arm
repo_two --> builder_arm
builder_arm --> repository_arm
repo_one --> builder_x86_64
repo_two --> builder_x86_64
builder_x86_64 --> repository_x86_64
Процесс обновления пакета
#
stateDiagram-v2
state "Добавление исходных кодов разработчика" as adding
state "Создание спецификации\nи локальная сборка пакета" as spec
state "Доработка спецификации сборки пакета" as local_build
state "Публикация пакета в публичном репозитории" as publish
state "Сборка на все целевые платформы" as farm_build
[*] --> adding
adding --> spec
spec --> local_build
local_build --> spec
local_build --> publish
publish --> farm_build
farm_build --> spec
farm_build --> [*]