Введение в POSIX'ивизм

       

Правила сборки


Ну все, с элементарным введением покончено. Переходим собственно к пакетам и их сборке.

Как явствует из названия (и из главы 1), все открытые и свободные программы и разделяемые библиотеки распространяются их разработчиками в исходных текстах. Конечно, никто не запрещает им создавать и прекомпилированные версии своих творений, и многие создатели программ так и поступают, предлагая один или несколько вариантов таковых, рассчитанные обычно на наиболее распространенные дистрибутивы Linux, иногда - FreeBSD, реже - другие BSD-системы (по причинам, которые станут ясными в последующем, прекомпилированные версии зависят от множества факторов, из которых целевая ОС - не последний). Однако это - скорее исключение чем правило.

Наборы исходников объединяются разработчиками в т.н. пакеты, о которых уже упоминалось на протяжении всего этого повествования. Пакет - понятие очень широкое и многогранное. Это может быть и простая монофункциональная утилита (например, строчный текстовый редактор ed или архиватор tar), более или менее обширный набор функционально связанных программ (скажем, coreutils) или огромный программный комплекс (примером чему - XFree86 или Xorg).

Следует оговориться, что термин пакет (английское package) постоянно употребляется в двух смыслах: как набор исходных текстов и как комплект скомпилированных из него программ и всех их служебных файлов. Обычно различие между ними ясно из контекста, в случаях же неоднозначности тот или иной смысл будет оговариваться явно.

Пакеты принято распространять в виде компрессированных архивов - файлов вида *.tar.gz (*.tgz) или *.tar.bz2 (*.tbz2, *.tbz), так называемых тарбаллов. Обычно действует правило: один тарбалл - один пакет. Очень большие пакеты могут быть поделены на несколько тарбаллов (примером чему те же XFree86 или Xorg), но делается это исключительно для удобства скачивания, все равно такой набор тарбаллов исходников сохраняет свою целостность.

Прекомпилированные пакеты подчас также распространяются разработчиками в виде точно таких же тарбаллов.
Но тут уже корреляции пакет - тарбалл может и не быть. Так, XFree86, кроме исходников, доступен также в виде серии скомпилированных пакетов для нескольких дистрибутивов Linux, Free- и OpenBSD. Но это уже - именно самостоятельные пакеты, и не все они обязательны к установке. А сборщики дистрибутивов могут и далее дробить изначально единый пакет, как это обычно делается с теми же Иксами.

И еще. В предыдущих главах я говорил, что BSD-системы, в отличие от Linux, не имеют пакетной организации. Это не совсем точно. Конечно, например, из FreeBSD Distributions нельзя выделить ядро системы или наборы базовых утилит в виде отдельных пакетов. Однако сам по себе он - в сущности единый пакет, только очень большой. А то, что перед пользователем (на CD ли диске, или на ftp-сервере) он предстает перед пользователем в виде кучи мелких (по 1,44 Мбайт) пакетиков - просто наследие тех времен, когда системы еще устанавливались с дискет. В NetBSD и OpenBSD же базовая система собрана в виде единого тарбалла (так и называемого - base.tgz), и уже совсем ничем не отличается от обычных пакетов.

В последнее время и в некоторых дистрибутивах Linux прослеживается тенденция отказа от "квантования" базовой системы. Так, в Gentoo она вся собрана в три тарбалла (stage1, stage2, stage3), и может быть развернута (с различной полнотой, в зависимости от схемы инсталляции) из любого из них. А в Sorcerer и его клонах базовый тарбалл вообще единственный.

Однако я отвлекся, вернемся к нашим исходниками и посмотрим, что с ними нужно делать - ведь ясно, что в том виде, в каком они распространяются, использование их невозможно.

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

  • конфигурирование;




  • собственно сборку;


  • инсталляцию.


  • Конфигурирование - это приведение программы в соответствие с реалиями конкретной системы. Как неоднократно говорилось, подавляющее большинство свободного софта пишется в рассчете на некую абстрактную POSIX-совместимую ОС.


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

    Понятие зависимостей - одно из основных при сборке программ. Суть его в том, что пакет pkgname1 для установки и (или) функционирования требует наличия в системе пакета pkgname2, тот, в свою очередь, может потребовать пакета pkgname3, и так далее. В качестве зависимостей выступают часто (хотя и не всегда) те самые системные библиотеки, о которых говорилось ранее.

    Зависимости пакетов бывают разными. С одной стороны, различают зависимости при сборке (обычно называемые просто зависимостями - depends) и зависимости при запуске (по английски именуемые run depends).

    Как следует из названий, при зависимости пакеты, от которых зависит данный, необходимы только на стадии сборки пакета, тогда как зависимости второго рода действуют постоянно. В большинстве случаев depends и run depends эквивалентны, однако это правило имеет многочисленные исключения. Так, при статической сборке (что это такое - будет говориться чуть позднее) библиотеки, которые использует данный пакет, требуются только в момент компиляции - в дальнейшем необходимости в них не возникает.

    С другой стороны, следует различать зависимости жесткие и "мягкие". Удовлетворение первых абсолютно необходимо для сборки данного пакета. Так, практически любая программа использует (статически или динамически) главную системную библиотеку glibc (или libc), любое приложение для системы X - главную Иксовую библиотеку xlib, все приложения для интегрированной среды KDE - библиотеки qt и kdelibc.

    "Мягкие" зависимости данного пакета не критичны для его функционирования - удовлетворение их лишь добавляет ему дополнительные функции (которые могут оказаться и лишними).

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


    В то же время пользователи Windows с ним сталкиваются очень редко, и потому постижение его вызывает определенные трудности у недавнего подоконника. Это связано с двумя факторами.

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

    Возможно, разработчик Windows-программы с удовольствием последовал бы примеру братьев-POSIX'ивистов. Однако исходники Windows-библиотек в большинстве своем закрыты и защищаются всякого рода проприетарными лицензиями, препятствующими их свободному использованию. И потому Windows-разработчику волей-неволей приходится реализовывать требуемые ему функции самостоятельно. В результате чего программа, хотя и приобретает определенную самодостаточность, но зато разбухает в размерах: вспомним, сколько файлов вида *.dll устанавливает элементарный графический вьювер, идущий в комплекте со сканером или цифровой камерой.

    За конфигурирование обычно отвечает сценарий, расположенный в корне дерева исходников данной программы и, по соглашению, носящий имя configure. Что именно делает конкретный конфигурационный скрипт - сугубо на совести разработчика программы. Как минимум, он обязан проверять жесткие зависимости устанавливаемого пакета и, при их нарушении, выдавать соответствующие сообщения. Кроме того, он может обеспечивать также подключение дополнительных функций - при наличии определенных условий, то есть удовлетворении зависимостей "мягких".

    Так, для консольных программ в Linux существует возможность использования мыши в качестве указательно-позиционирующего устройства (а не только для выделения/вставки экранных фрагментов, как это имеет место в BSD-системах).


    Обеспечивается эта функция специальным сервисом - gpm. И во многих программах (таких, как файловый менеджер Midnight Commander или текстовый браузер links) конфигурационный скрипт проверяет, установлен ли пакет gpm, и при наличии его - автоматически задействует использование мыши.

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

    Если конфигурирование прошло с ошибками, выдается соответствующее сообщение, форма которого также целиком определяется разработчиком. Ошибки эти могут быть связаны с нарушением жестких зависимостей пакета, и в этом случае никакие дальнейшие действия, до их разрешения, невозможны. Если же конфигурационный сценарий выявил нарушение "мягких" зависимостей, то пользователь обычно может отказаться от них, просто потеряв некоторую дополнительную функциональность. Которая, к тому же, вполне может быть ему не нужной. Так, например, я всегда отказываюсь от поддержки мыши (через gpm) в консольных Linux-программах. Правда, это может потребовать указания некоторых опций конфигурирования (о чем я скажу чуть ниже). Правда, обычно это требует указания некоторых дополнительных опций исполнения скрипта configure, о которых будет сказано ниже.

    Образцово-показательный отчет о выполнении сценария configure выдают, по моему мнению, пакеты, штатно входящие в состав интегрированной среды KDE. Во-первых, их конфигурирование не обрывается сразу же после нахождения первой ошибки (первого нарушения зависимостей - например, отсутствия каких-либо мультимедийных или графических библиотек), как это бывает в большинстве других программ, а в любом случае доводится до конца. После чего сообщается, что такие-то компоненты необходимы для сборки данного пакета (то есть связаны с ним жесткими зависимостями), другие же - требуются для получения определенных функций (например, наличие пакета cups - для обеспечения печати на принтере, пакета sane - для сканирования, и так далее).


    И пользователю вольно решить - устанавливать ли ему "мягко-зависимые" пакеты, или он, за отсутствием сканера или принтера, вполне может обойтись без них.

    Наконец, конфигурирование завершилось успешно. Наступает время следующего этапа - собственно сборки, то есть претворения исходных текстов программы в исполняемый машинный код. Этап этот распадается на несколько стадий.

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

    И потому вторая стадия - это связывание (linking, в просторечии именуемое линковкой) сгенерированного кода с необходимыми библиотечными фрагментами. Линковка может быть двух видов - статическая и динамическая. В первом случае требуемый код из библиотеки встраивается внутрь собираемой программы, после чего получается готовый к исполнению бинарный файл, более в библиотеке не нуждающийся. Это - именно тот случай, когда понятия depends и rdepends приобретают разное значение: первое оказывается шире.

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

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


    И потому именно она преимущественно используется при сборке программ для свободных POSIX-систем (повторю еще раз, что разделяемые библиотеки в них открыты и могу применяться без ограничений).

    Однако бывают ситуации, когда приходится прибегать к линковке статической. Так, во FreeBSD статически линкуются с главной системной библиотекой жизненно важные для запуска и восстановления системы утилиты (в предыдущих версиях этой ОС они располагались в каталогах /bin и /sbin, во FreeBSD 5-й ветки для них отведен специальный каталог /restore). В результате они оказываются доступными (и пригодными к исполнению) даже в случае аварийной загрузки, когда все файловые системы, кроме корневой, не монтируются (а разделяемые библиотеки вполне могут располагаться на самостоятельных физических носителях со своими файловыми системами).

    Третий этап процесса сборки - инсталляция. Это - инкорпорация всех компонентов программы в структуру файловой системы данной машины. Или, по простому, по бразильскому - их копирование в соответствующие каталоги файлового древа (по завершении сборки они могут находиться в самых разных местах, обычно - в подкаталогах дерева исходников). Как правило, для разных компонентов пакета существуют традиционно предопределенные имена каталогов, в которых они должны размещаться: bin или sbin - для исполняемых модулей, lib - для библиотек, etc - для конфигурационных файлов, share - для всякого рода документации и примеров, и так далее.

    Предопределенные имена каталогов не обязательно будут ветвями корня файловой системы (типа bin, /sbin и так далее). Точнее, в общем случае, не будут: более вероятно, что соответствующие компоненты собираемого пакета помещаются в каталоги /usr/bin, /usr/local/bin и так далее. Впрочем, к обсуждению этого вопроса мы скоро вернемся.

    Так вот, процесс инсталляции и сводится к тому, что исполняемый файл (файлы) собранного пакета копируется в файл ~/bin (или, для программ системного назначения, в ~/sbin), ее конфиг - в ~/etc, страницы документации - в ~/man, и так далее (~/ в данном случае символизирует не домашний каталог пользователя, а некий условный префикс - см.далее).

    По завершении всего сборочного цикла из пакета исходников должна получиться полнофункциональная, готовая к употреблению, программа, требующая лишь некоторой пользовательской настройки.


    Содержание раздела