Лекция 2. Сборочные зависимости и инструменты работы с ними
Вы попали на цикл лекций UNEEX.
Мы подсократим тему разработки под Linux. У нас есть сервер, куда можно зайти по ssh и чего-то поделать, и вот ровно то, что можно делать по ssh, мы и будем изучать, хотя этим, конечно, разработка под Linux не ограничивается. В прошлый раз поговорили про IDE. Что нужны компиляторы, библиотеки, текстовые редакторы специального вида. Редакторов мы насчитали примерно 3. Про библиотеки сказали, что для разработки нужна ещё и devel версия библиотеки.
Тема этой лекции — сборочные зависимости.
Этот термин троякий или как минимум двоякий.
С одной стороны сборочными зависимостями называют всё то, что надо установить в окружении, чтобы программа собралась. Текстовый редактор может не стоять, а программа собираться.
Во-вторых, это порядок сборки. Если вы будете собирать программу из одного файла, то cc o.c сделает один файл a.out. В реальной жизни такая лафа быстро заканчивается. Выясняется, что в вашем проекте не один и не три файла, а 500, и не только на C, а ещё на каком-нибудь Bison. В какой-то момент вы приходите к ситуации, когда у вас много файлов, из них получаются промежуточные объектники и потом уже бинарники. Вы сами порядок сборки ещё помните, а вот товарищу на словах уже не объяснить. Да и перекомпилировать все 500 файлов не всегда нужно и хочется.
Казалось бы, есть правила сборки — забей в shell-скрипт последовательность и собирай. Первая проблема такого подхода — скорость, вторая — порядок.
Итого, какое такое «всё» надо установить в систему, чтобы ваша программа собиралась?
В простом случае всё понятно — используется библиотека, надо поставить её и её devel версию.
В плохом случае мы не знаем какие именно библиотеки нам нужны, стоят они или нет. Более того, для сложных случаев некоторые библиотеки могут быть опциональны и их необходимость/допустимость зависеть, например, от операционной системы, под которую собирается проект.
Но проблемы с порядком сборки возникают раньше, чем проблемы с «установить всё».
Назовем эти проблемы.
- Определить граф зависимости ваших файлов, как исходных, так и промежуточных (генератов), и таргет — цель сборки. Порядок сборки влечет за собой понятие «зависимость по сборке».
Проверка старшинства файлов. Пусть есть цепочка .y -> .c -> .o -> bin. Если объектный старше бинарного, то бинарный актуален. Если при этом .y более молодой — вся цепочка нуждается в перекомпиляции. Старшинство должно быть .y < .c < .o < bin. Соответственно, сначала строим граф, потом в нём проверяем старшинство.
- Ещё не должно быть циклической зависимости. Система должна отслеживать, где у наших куриц и яиц пращур.
А ещё, математика это хорошо, но ещё нужны какие-то действия. Стрелочки стрелочками, но за ними должны стоять команды. Как ни удивительно, это оказываются команды на shell.
Это мы с вами make изобретаем, если ещё никто не понял. Его идея вырастает из перечисленных выше трех пунктов. Этот инструмент позволяет указать, что файлы такого-то типа получаются из файлов такого-то типа вызовом такой-то команды. А потом позволяет задать, что мы хотим получить. В сложном случае человек не может осмыслить весь путь зависимостей, поэтому make думает за него — всё ли готово, или надо какую-то зависимость по дереву дособрать?
Make-файлы можно использовать для разных вещей, например, когда вы хотите описать процесс через некоторые подпроцессы (пример — сборка дистрибутива).
Что сюда можно привнести реально удобного, чего не хватает?
Напоминаю, мы делаем инструмент, который позволяет собирать что-либо, указывая только зависимости и способы преобразования.
- Чистка. Неплохо было бы удалять генераты, все или не совсем все. Есть одна проблема. У удаленного файла нет таймстемпа.
clean: rm -f *.o
Файл clean в результате этого не появится, да и зависимость никакая его не потребует. Поэтому.phony clean: rm -f *.o
Указывает, что от этой цели не надо ждать появления целевого объекта. - Переменные.
-o$@ — target, значок, похожий на мишень. -с$< — source
%.o: %.c $(CC) -c$< -o$@
«Таргет получается из сорса вызовом команды $(CC)». Подстановка происходит тогда, когда выполняется команда — поздняя подстановка.Если нужна ранняя подстановка, то вместо = используется := . На сегодняшний день документация к GNU make весьма обширна. Много разных способов обработки текстовой информации при подстановке. Можно, например, написать так:
SRC = *.c OBJ = $(SRC:*.o=*.c)
Превратить список сишников в список объектников. Такого барахла, очень удобного в очень редких (или не очень) случаях, там очень много.Ещё make может запускаться покаталожно, и при этом будет наследовать все переменные, которые сгенерились при предыдущих вызовах.
То есть, если сказать make CC=clang, то CC переопределится, даже если в make-файле было явное присваивание CC.
- Генерация зависимостей.
.cpp всегда зависит от .h, если ашники есть. Поэтому такого рода зависимости не надо писать руками, их генерация автоматизирована.
Теперь к собираемости проекта.
Задача номер ноль — проверка наличия библиотек по списку. Скрипт, который бы лез и проверял есть ли библиотека, есть ли к ней ашник. Если проявить чудеса программистской мысли, то скрипт на выходе сгенерит ашник с указанием, что есть. Но такая деятельность очень неблагодарная и абсолютно не кроссплатформенная — мало ли где чего лежит.
Переходим к следующему этапу Мерлезонского балета — не как обеспечить собираемость, а как сделать файл, который позволит обеспечить собираемость.
autoconf
Чуть-чуть буду рассказывать про такую штуку, которая называется autotools, в народе также именуемая autocrap.
- autoconf . Такой профиль, в котором все системнозависимые места стыдливо прикрыты макросами. Зачем? Привязка к расположению библиотек, привязка к компиляторам — всё отвязывается. Какой бонус и какой деградейшн? Make-файл не пишем, его генерит configure, а перед configure наличие инструментов проверяем отдельным инструментом. Деградейшн — в получившемся make-файле есть много останков роботов, и править его руками очень неприятно, но иногда приходится. Чего нам не сделает один autoconf, без товарищей? Не сгенерит зависимость от заголовков. Неплохо было бы, что бы по инклюду что-то догадывалось, какие заголовочные файлы нужны. Такой инструмент называется
- autoscan
- automake, А ещё хочется make-файл получать следуюшим образом — вот там у нас бинарник, вот там исходник, а сделайте нам make-файл. Для него правда нужно писать профиль, в котором высказывать разумные хотелки.
autoconf написан на M4, automake на Перле. Это можно читать, но, как это сказать, надо уметь.
Таким образом, вы описываете не структуру проекта, а разумные детали. Если вы положили в чан достаточное количество дохлых мышей, то общаться нужно будет только с верхним уровнем am, и это будет достаточно нормально. Но configure будет после применения всех этих инструментов очень страшно.
Очевидно, это инструменты для поддержки разработки, для собственно сборки достаточно make-файла.
Есть холивар — что считать исходником, а что генератом. configure это просто шелл-скрипт, даром что генерат, его можно распространять, для выполнения он не требует остатков стека. Некоторые люди понимают, что configure это генерат и говорят — ставьте автотулз и перегенеряйтся все сначала.
- aclocal — создает кэш M4-макросов, актуальный для вашей системы.
Для тех, кто ещё не запутался, на Википедии есть граф запуска разных инструментов autotools и зависимостей их генератов.
Много специфики здесь имплицировано заточенностью под C. Например, тем фактом, что в C стараются компилироваться с системными библиотеками, а не так, как, например, на каком-нибудь Ruby, где библиотеки таскают с собой.
В домашнем задании два пункта про autocrap — собрать GNU Hello. Задание повышенной сложности — справиться с сообщением, что у вас в системе autotools новее, чем то, что вы тут пытаетесь использовать.
autoreconf -fisv
- libtool. Сборка библиотеки это отдельная штука. Это всегда системнозависящая вещь. Процедура компоновки и архивации это вообще дело даже не компилятора, а binutils, которых много разных.
Вопрос. Нельзя ли проще?
Зависит от того, чего мы хотим достичь. Если мы просто хотим ключи для сборки с конкретной библиотекой, или узнать где лежат инклюдники. Такая штука называется pkg-config. Кроме того, вышеописанный стек очень C-ориентирован. Если вы пишете на джаве, то это стремительно превращается в тыкву. В интегрированных средах обычно есть что-то свое. В джаве есть ant, в Ruby rake, и т.д. Причем они повторяют весь этот стек — и привязка к среде, и генерация make-файла по структуре проекта.
В противовес этому инструментарию изобретают и другие, для рещения того же списка задач, но более простым путем. Из известных лектору это scons и cmake. Приятная особенность — автотулзами можно пользоваться только после просветления, а писать проекты нужно уже сейчас. Так что цель — чтобы пользователь мог написать простой аналог am файла, специально обученная утилита зажует этот файл и начнет порождать сначала процедуры для проверки наличия зависимостей, параллельно генерить граф сборочных зависимостей, потом будет сгенерирован какой-нибудь бэкенд для системы сборки низкого уровня, например для make’а. Или проджект от Visual C++, или ещё чего. Это сильно повышае кроссплатформенность. Очень смешная страничка про cmake на русской Википедии (странно, что до неё не добралась википолиция.) Основное отличие scons, что он написан на Питоне, а cmake на C++. Из больших проектов Qt переехало на cmake. В итоге с точки зрения пользователя у вас все одинаково, а сборочные среды совершенно разные — код проектов на Qt может быть одинаковым для вин и лин (хотя тут ещё и особенность самого Qt, в котором есть всё своё).
out-of-tree сборка
scons и cmake загаживают исходники генератами. Кроме того эти генераты убирают в git ignore, и пока глазами не посмотришь, не поймешь, что там дочерта генератов хранится. out-of-tree — сборка в свежеочищенном каталоге. Это всё пляшет от идеи репродуцииромости сборки — когда скачали с github’а и хотим где угодно собрать.
Автотулзы очень крутые, с их помощью можно сделать out-of-tree сборку. А scons с cmake’ом не очень крутые, на них это делается само.
Домашнее задание — Собрать GNU Hello с помощью autocrap’а. Собрать пример на cmake. Задание для тех, кому неинтересно копипастить. В документации по terminfo и по Bison’у есть примерчики, их надо скомпилировать. Для программы на Bison’е написать make-файл. Если окажется слишком просто — написать cmake-файл.
Домашние задания нужны для того, чтобы пощупать упоминаемое на лекции. Если вы руками не потрогаете, это все останется словами. Изучать разработку исключительно со слов лектора — не самая лучшая идея.