Лекция 2. Сборочные зависимости и инструменты работы с ними

Вы попали на цикл лекций UNEEX.

Мы подсократим тему разработки под Linux. У нас есть сервер, куда можно зайти по ssh и чего-то поделать, и вот ровно то, что можно делать по ssh, мы и будем изучать, хотя этим, конечно, разработка под Linux не ограничивается. В прошлый раз поговорили про IDE. Что нужны компиляторы, библиотеки, текстовые редакторы специального вида. Редакторов мы насчитали примерно 3. Про библиотеки сказали, что для разработки нужна ещё и devel версия библиотеки.

Тема этой лекции — сборочные зависимости.

Этот термин троякий или как минимум двоякий.

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

Во-вторых, это порядок сборки. Если вы будете собирать программу из одного файла, то cc o.c сделает один файл a.out. В реальной жизни такая лафа быстро заканчивается. Выясняется, что в вашем проекте не один и не три файла, а 500, и не только на C, а ещё на каком-нибудь Bison. В какой-то момент вы приходите к ситуации, когда у вас много файлов, из них получаются промежуточные объектники и потом уже бинарники. Вы сами порядок сборки ещё помните, а вот товарищу на словах уже не объяснить. Да и перекомпилировать все 500 файлов не всегда нужно и хочется.

Казалось бы, есть правила сборки — забей в shell-скрипт последовательность и собирай. Первая проблема такого подхода — скорость, вторая — порядок.

Итого, какое такое «всё» надо установить в систему, чтобы ваша программа собиралась?

В простом случае всё понятно — используется библиотека, надо поставить её и её devel версию.

В плохом случае мы не знаем какие именно библиотеки нам нужны, стоят они или нет. Более того, для сложных случаев некоторые библиотеки могут быть опциональны и их необходимость/допустимость зависеть, например, от операционной системы, под которую собирается проект.

Но проблемы с порядком сборки возникают раньше, чем проблемы с «установить всё».

Назовем эти проблемы.

  1. Определить граф зависимости ваших файлов, как исходных, так и промежуточных (генератов), и таргет — цель сборки. Порядок сборки влечет за собой понятие «зависимость по сборке».
  2. Проверка старшинства файлов. Пусть есть цепочка .y -> .c -> .o -> bin. Если объектный старше бинарного, то бинарный актуален. Если при этом .y более молодой — вся цепочка нуждается в перекомпиляции. Старшинство должно быть .y < .c < .o < bin. Соответственно, сначала строим граф, потом в нём проверяем старшинство.

  3. Ещё не должно быть циклической зависимости. Система должна отслеживать, где у наших куриц и яиц пращур.

А ещё, математика это хорошо, но ещё нужны какие-то действия. Стрелочки стрелочками, но за ними должны стоять команды. Как ни удивительно, это оказываются команды на shell.

Это мы с вами make изобретаем, если ещё никто не понял. Его идея вырастает из перечисленных выше трех пунктов. Этот инструмент позволяет указать, что файлы такого-то типа получаются из файлов такого-то типа вызовом такой-то команды. А потом позволяет задать, что мы хотим получить. В сложном случае человек не может осмыслить весь путь зависимостей, поэтому make думает за него — всё ли готово, или надо какую-то зависимость по дереву дособрать?

Make-файлы можно использовать для разных вещей, например, когда вы хотите описать процесс через некоторые подпроцессы (пример — сборка дистрибутива).

Что сюда можно привнести реально удобного, чего не хватает?

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

  1. Чистка. Неплохо было бы удалять генераты, все или не совсем все. Есть одна проблема. У удаленного файла нет таймстемпа.
    clean:
        rm -f *.o
    Файл clean в результате этого не появится, да и зависимость никакая его не потребует. Поэтому
    .phony clean:
        rm -f *.o
    Указывает, что от этой цели не надо ждать появления целевого объекта.
  2. Переменные.

     -o$@ — target, значок, похожий на мишень. -с$< — source

    %.o: %.c
        $(CC) -c$< -o$@
    «Таргет получается из сорса вызовом команды $(CC)». Подстановка происходит тогда, когда выполняется команда — поздняя подстановка.

    Если нужна ранняя подстановка, то вместо = используется := . На сегодняшний день документация к GNU make весьма обширна. Много разных способов обработки текстовой информации при подстановке. Можно, например, написать так:

    SRC =  *.c
    OBJ = $(SRC:*.o=*.c)
    Превратить список сишников в список объектников. Такого барахла, очень удобного в очень редких (или не очень) случаях, там очень много.

    Ещё make может запускаться покаталожно, и при этом будет наследовать все переменные, которые сгенерились при предыдущих вызовах.

    То есть, если сказать make CC=clang, то CC переопределится, даже если в make-файле было явное присваивание CC.

  3. Генерация зависимостей.

    .cpp всегда зависит от .h, если ашники есть. Поэтому такого рода зависимости не надо писать руками, их генерация автоматизирована.

Теперь к собираемости проекта.

Задача номер ноль — проверка наличия библиотек по списку. Скрипт, который бы лез и проверял есть ли библиотека, есть ли к ней ашник. Если проявить чудеса программистской мысли, то скрипт на выходе сгенерит ашник с указанием, что есть. Но такая деятельность очень неблагодарная и абсолютно не кроссплатформенная — мало ли где чего лежит.

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

autoconf

Чуть-чуть буду рассказывать про такую штуку, которая называется autotools, в народе также именуемая autocrap.

autoconf написан на M4, automake на Перле. Это можно читать, но, как это сказать, надо уметь.

Таким образом, вы описываете не структуру проекта, а разумные детали. Если вы положили в чан достаточное количество дохлых мышей, то общаться нужно будет только с верхним уровнем am, и это будет достаточно нормально. Но configure будет после применения всех этих инструментов очень страшно.

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

Есть холивар — что считать исходником, а что генератом. configure это просто шелл-скрипт, даром что генерат, его можно распространять, для выполнения он не требует остатков стека. Некоторые люди понимают, что configure это генерат и говорят — ставьте автотулз и перегенеряйтся все сначала.

Для тех, кто ещё не запутался, на Википедии есть граф запуска разных инструментов autotools и зависимостей их генератов.

Много специфики здесь имплицировано заточенностью под C. Например, тем фактом, что в C стараются компилироваться с системными библиотеками, а не так, как, например, на каком-нибудь Ruby, где библиотеки таскают с собой.

В домашнем задании два пункта про autocrap — собрать GNU Hello. Задание повышенной сложности — справиться с сообщением, что у вас в системе autotools новее, чем то, что вы тут пытаетесь использовать.

autoreconf -fisv

Вопрос. Нельзя ли проще?

Зависит от того, чего мы хотим достичь. Если мы просто хотим ключи для сборки с конкретной библиотекой, или узнать где лежат инклюдники. Такая штука называется 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-файл.

Домашние задания нужны для того, чтобы пощупать упоминаемое на лекции. Если вы руками не потрогаете, это все останется словами. Изучать разработку исключительно со слов лектора — не самая лучшая идея.

LecturesCMC/LinuxApplicationDevelopment2012/Conspects/02 (последним исправлял пользователь Nyarcel 2012-10-23 08:10:09)