§ 8. Системы сборки кода
Введение
01 Системы сборки кода — это специальные программы, которые собирают и пересобирают код проекта в автоматическом режиме по заранее заданным правилам. Эти системы определяют зависимости между файлами с исходным кодом и выходными файлами (программами, библиотеками и конфигурационными файлами) и в нескольких параллельных процессах выполняют команды компиляции для всех изменившихся со времени последней сборки файлов, соблюдая зависимости. Вторая задача систем сборок — это поиск в операционной системе и подключение к проекту библиотек и программ, которая реализуется наиболее удобными способами в зависимости от операционной системы.
02 В новых языках программирования (Rust, Go) параллельная сборка и поиск зависимостей уже встроены, но для существующих языков (C, C++, Fortran) это невозможно сделать, поэтому для них используют отдельные системы сборки. Про них и пойдет речь в этой лекции.
03 Система сборки является самым важным элементом любого проекта. Эта программа генерирует команды для сборки исходного кода, и чем быстрее эта система собирает код и чем больше рутинных операций автоматизирует, тем быстрее идет разработка, и тем проще настроить непрерывную интеграцию — автоматизированную сборку и тестирование вашей программы. В задачи системы сборки входит
- поиск зависимостей (заголовочных файлов и библиотек),
- генерация различных версий кода в зависимости от платформы, на которой происходит сборка,
- генерация вспомогательных файлов,
- генерация команд для компиляции всех исходный файлов.
04 Как правило, системы сборки поддерживают опции для включения или отключения различных компонент программы. Результатом работы системы сборки является директория, в которой находятся сгенерированные файлы, а также файл с дальнейшими командами для подчиненной (более низкоуровневой) системы сборки. К высокоуровневым системам относятся Autoconf, Cmake, Meson, к низкоуровневым — Make, Ninja. Мы будем изучать Make, Meson и Ninja, а также программу для работы с зависимостями Pkg-config.
Make
05 Классической, а также самой простой системой сборки является GNU Make. Правила сборки для этой системы описываются в файле Makefile, и состоят из списка файлов, которые генерирует (выходные файлы) команда, двоеточия, списка файлов, которые команда использует (входные файлы) для генерации и команд генерации. Если время последнего обновления хотя бы одного входного файла позже времени последнего обновления хотя бы одного выходного файла, то команда выполняется. В противом случае выходные файлы считаются актуальными. Все команды в файле начинаются с символа табуляции.
06 Часто в списке выходных файлов указывают несуществующий файл, тогда команда запускается каждый раз, когда вводится команда make имя-файла
. По умолчанию команда make
собирает первый файл.
all: echo Hello main.copy: main cp main main.copy
make all # сборка файла all make # сборка файла all make main.copy # сборка файла main.copy
07 Поскольку Make был разработан для сборки программ, то в него встроены удобные правила для компиляторов, которые работают даже без наличия в директории Makefile. Например, команда make main.o
запустит команду компиляции прогаммы $CXX $CXXFLAGS -c -o main.o main.cc
. Здесь компилятор и флаги компиляции задаются переменными среды CXX
и CXXFLAGS
, а входной файл определяется автоматически по названию выходного. Если переменные не определены, то используется компилятор по умолчанию (GCC) и флаги по умолчанию (по умолчанию их нет). Команда make main
запустит коману линковки программы $CC $LDFLAGS -o main main.o
. Встроенного правила для сборки библиотек в Make нет, но его легко написать самостоятельно.
make main.o # компиляция main.o из main.cc env CXXFLAGS='-O3' make main.o # компиляция main.o из main.cc с максимальным уровнем оптимизации env CXX='clang++' make main.o # компиляция main.o из main.cc с помощью компилятора clang++ make main # линковка main из main.o env CC=g++ LDFLAGS='-flto' make main # линковка main из main.o с флагом оптимизации
08 Переменные среды, используемые Make для сборки программ, стали стандартом и для других систем сборки. Практически все системы сборки их поддерживают. Эти переменные чаще всего используются мейнтенерами пакетов для различных дистрибутивов Linux: в этих переменных указываются флаги, которые используются для сборки всех пакетов дистрибутива. Например, в некоторых системах используется флаг -fstack-protector-strong
, который позволяет получить защиту от взлома программ даже при наличии в них ошибок работы с памятью.
Переменная | Описание |
---|---|
CC | компилятор языка C и линковщик для всех языков |
CFLAGS | флаги компилятора языка C |
CXX | компилятор языка C++ |
CXXFLAGS | флаги компилятора языка C++ |
FC | компилятор языка Fortran |
FFLAGS | флаги компилятора языка Fortran |
LDFLAGS | флаги линковщика |
09 Основным недостатком Make является чрезмерная простота команд: в правилах сборки не учитываются заголовочные файлы, которые включаются в основные файлы с помощью макросов. Это означает, что при изменении заголовочного файла пересборка основного не произойдет. Это послужило причиной создания более совершенных и высокоуровневых систем сборок. Тем не менее, Make до сих пор используется в небольших проектах, в том числе, в проектах, где нужно собирать не код, а что-то другое. Причина этому — простота использования и наличие Make во всех дистрибутивах Linux.
Meson и Ninja
10 Любой проект Meson начинается с дерева директорий, в каждой из которых находится файл для сборки. В meson это файл meson.build. В проектах на C++ с небольшими вариациями используется следующее дерево директорий.
doc # документация src ├── test # код модульных тестов │ ├── component1 # структура папок такая же, │ ├── component2 # как и в основном коде │ └── component3 └── myproject # основной код ├── component1 ├── component2 └── component3
11 Здесь myproject — название проекта, componentN
— логическая единица проекта. Как правило, каждая компонента собирается в отдельную библиотеку (или исполняемый файл), которая затем присоединяется к основному исполняемому файлу. В больших проектах из одной компоненты могут получиться несколько библиотек или исполняемых файлов. При такой схеме название проекта и название компоненты являются частью пути до заголовочного файла, а имя файла совпадает с именем класса, который в нем объявлен. В больших проектах кроме класса в файле могут быть объявлены вспомогательные функции или классы. Чтобы использовать класс Ship
из компоненты component1 в каком-либо файле проекта, его следует подключить, используя путь относительно директории src.
#include <myproject/component1/ship.hh>
12 В небольших проектах структура упрощается путем исключения директорий компонент и хранении всех файлов с исходным кодом в директории myproject. Именно такую структуру я вам рекомендую использовать в заданиях.
13 Корневой файл meson.build для вышеописанной упрощенной структуры содержит параметры проекта: название, версию, язык, опции сборки по умолчанию. Команда project
должна быть первой командой в корневом файле. Команда subdir
исполняет команды из файла meson.build в указанной в качестве аргумента директории.
project( 'myproject', # название проекта 'cpp', # язык version: '0.1.0', # версия кода meson_version: '>=0.50', # минимально поддерживаемая версия Meson default_options: ['cpp_std=c++20'] # используемый стандарт C++ ) subdir('src')
# сохранение пути до текущей директории # для подключения заголовочных файлов # в виде <myproject/...> src = include_directories('.') subdir('myproject')
myproject_src = files([ 'main.cc' # список исходных файлов ]) executable( 'myproject', # название исполняемого файла include_directories: src, # где ищутся заголовочные файлы sources: myproject_src, # список исходный файлов dependencies: [], # зависимости проекта (если имеются) install: true # устанавливать ли файл )
Для инициализации директории, в которой будет происходить сборка, нужно ввести следующие команды в корне проекта.
meson . build
cd build
ninja
После первичной инициализации после любого изменения кода достаточно набрать ninja
для пересборки проекта. При этом пересоберется только измененная и зависимые от нее части кода.
Pkg-config
14 Для того чтобы подключить библиотеку к вашей программе, достаточно указать директорию с заголовочными файлами, директорию с библиотекой, а также название библиотеки с помощью опции -l
. Если библиотека установлена в систему стандартным способом, то, как правило, достаточно только названия. Процесс подключения библиотек к проекту автоматизируется с помощью программы pkg-config
, которая по названию пакета выдает флаги компилятора и флаги линковщика, которые необходимы для корректной сборки проекта с этой библиотекой.
Команда | Описание |
---|---|
pkg-config --cflags OpenCL | вывести флаги компилятора для подключения библиотеки OpenCL |
pkg-config --libs OpenCL | вывести флаги линковщика для подключения библиотеки OpenCL |
pkg-config --cflags 'OpenCL >= 1.2' | вывести флаги компилятора для подключения библиотеки OpenCL версии не ниже 1.2 |
pkg-config --list-all | вывести список библиотек, установленных в системе и имеющих файл pkg-config |
pkg-config
для подключения библиотек.15 Файл pkg-config
для своего проекта обычно делают из шаблона, в котором прописываются директории, в которые устанавливается проект. В Meson это можно сделать автоматически с помощью модуля pkgconfig
. Файл установится также автоматически при установке всего проекта.
Name: OpenCL Description: Open Computing Language Client Driver Loader Version: 2.2 Libs: -L/usr/lib64 -lOpenCL Cflags: -I/usr/include
pkgconfig = import('pkgconfig') pkgconfig.generate( unistdx_lib, version: meson.project_version(), name: 'unistdx', description: 'C++ wrappers for libc', )
16 Библиотеки также можно автоматически искать с помощью pkg-config
. В Meson это команда dependency
. В случае с Make команды поиска прописываются в CXXFLAGS и LDFLAGS.
env CXXFLAGS="$(pkg-config --cflags OpenCL)" LDFLAGS="$(pkg-config --libs OpenCL)" make
OpenCL = dependency('OpenCL') executable( ... dependencies: [OpenCL], ... )
Задания
Make1 балл
17 Напишите Makefile с правилами для сборки программы, состоящей из файлов main.cc и main.hh: первый файл включает второй с помощью #include
. Программа должна пересобираться при изменении main.hh. Команды сборки писать не надо, просто укажите зависимости между файлами.
#include "main.hh" int main() { return 0; }
Make + pkg-config1 балл
18 Соберите код из первого задания с библиотекой zlib. Библиотеку подключите с помощью pkg-config
.
Meson1 балл
19 Соберите код из первого задания с библиотекой zlib с помощью Meson.
Git + Meson2 балла
20 Скачайте проект unistdx с помощью команды git
. Соберите его с помощью Meson с флагами компилятора -march=native -O3 и флагом линковщика -flto.