|
Thursday, 1 November. 2007Solaris. Основы сборки приложений из исходных кодовАвтор: Афанасьев Андрей a.k.a scum 1. Введение. Начинающие системные администраторы часто задают множество вопросов, связанных с проблемами, возникающими в ходе самостоятельной сборки программных пакетов. Иногда, для успешного завершения компиляции и последующей компоновки приходится самостоятельно вносить исправления в исходный код. Несомненно, решение подобных задач требует наличия у администратора некоторого опыта в программировании. Но не все так страшно, как кажется на первый взгляд. Подавляющее большинство программных проектов, распространяющихся в исходных кодах, проходит всестороннее тестирование перед предоставлением их в публичный доступ и не содержат явных ошибок, которые начинают проявлять себя уже на этапе компиляции. Обычно работа с такими программами не вызывает проблем у администраторов со стажем, которые знают основной принцип успешной сборки: правильную настройку рабочего окружения. Опытный администратор всегда знает, какие утилиты и в каких случаях следует использовать, какие переменные окружения влияют на процесс сборки, где следует искать необходимые библиотеки и заголовочные файлы и т.д.
Поскольку большинство системных администраторов не являются одновременно и программистами, они обычно накапливают подобные знания с нуля – на основании собственного опыта, действуя по принципу проб и ошибок. Несомненно, это верный, но долгий путь. Попробуем по возможности сократить его. Для этого ненадолго представим себя на месте программиста. Мы напишем небольшую программу и на ее основе рассмотрим типичные задачи сборки приложений, а также определим и попытаемся решить сопутствующие проблемы. Нет, эта статья не является пособием для начинающего программиста. Нашей основной задачей не является обучение программированию – вместо этого мы подробно проследим весь путь преобразования исходного кода в готовую программу. Конечно, в ходе повествования мне придется рассмотреть базовые моменты, связанные с программированием. Однако я попытаюсь изложить материал по возможности максимально доступно. Так что от читателя не потребуется ни знаний основных концепций программирования, ни языка С, на базе которого и будет построен наш небольшой учебный проект. 2. Первые шаги. Итак, приступим. Но сначала хотелось бы подробнее рассмотреть основные этапы сборки программы. Попутно мы познакомимся с необходимыми терминами, которые я буду представлять как в русскоязычной, так и в англоязычной нотации. На первом этапе происходит предварительная обработка исходного кода программой-препроцессором (preprocessing). Работу этой утилиты мы скоро рассмотрим подробнее, но сейчас хотелось бы отметить, что во время сборки ошибки, генерируемые препроцессором, являются одними из наиболее распространенных. В основном причиной их возникновения является невозможность нахождения необходимых заголовочных файлов. К счастью, подобные сообщения достаточно красноречивы, что позволяет быстро локализовать и устранить возникшую проблему. Обработанный препроцессором исходный код затем передается на обработку компилятору, который преобразует его в машинный формат, т.е. формирует так называемый объектный код (object code). Такой процесс называется компиляцией (compiling). Критические сообщения, возникающие на этапе компиляции, чаще всего свидетельствует о наличии ошибок, допущенных программистом непосредственно при написании программного кода. После компиляции следует процесс компоновки (linking). На этом этапе происходит окончательная сборка итоговой программы из отдельных модулей. Компоновка – наиболее сложный процесс из всех перечисленных. Иногда ошибки, возникающие на этом этапе, способны поставить в тупик даже опытного программиста. В дальнейшем мы рассмотрим типовые проблемы, возникающие при компоновке, а также пути их решения. Теперь давайте напишем нашу первую тестовую программу. #include <stdio.h> Листинг 2.1 hello.c Рассмотрим приведенный код. Начинается программа с директивы #include, содержащей указание препроцессору на совершение операции подстановки в исходный код содержимого специального заголовочного файла (header file) stdio.h. Что это за файл и где его можно найти в системе? Обратите внимание, что его название заключено в угловые скобки. Это говорит о том, что в данном случае речь идет о системном файле, который нужно искать в соответствующем системном каталоге. В Solaris таким каталогом является /usr/include. Нам нужен stdio.h, так как в нем находится объявление (declaration) функции printf(). Так как С является строгим языком, то перед использованием любой функции он требует ее объявления, которое в случае с функцией printf() выглядит так: extern int printf(const char *_RESTRICT_KYWD, ...); Данное объявление, несмотря на его устрашающий для непосвященного вид, просто означает, что функция printf() должна принимать на входе символьную строку (char), после которой может следовать любое количество дополнительных аргументов (помечены …), а после своего завершения она возвращает некоторое число (int). Язык С требует обязательного наличия объявлений для того, чтобы иметь возможность обнаруживать ошибки программиста уже на этапе анализа исходного кода. Таким образом, если бы я ошибся при написании функции printf(), например, опечатавшись при наборе ее имени, компилятор сразу же предупредил бы меня об этом. После директивы препроцессора следует основная функция программы – main. Именно внутри нее и располагается исполняемый код, который в нашем случае состоит из вызова двух процедур. Первая из них – printf(), выводящая на экран приветственное сообщение. Вторая процедура – return. Она сигнализирует об успешном завершении нашей программы. Это встроенная функция языка С, поэтому ее объявления не требуется. На первый раз достаточно теории. Давайте вернемся к нашему практическому примеру. Сохраним исходный код в текстовый файл с названием hello.c и откомпилируем его. Для этого, конечно же, нам потребуется компилятор. В случае с Solaris у нас есть выбор между двумя утилитами: gcc – компилятор языка C от сообщества GNU, находится в каталоге /usr/sfw/bin, сс – компилятор, входящий в состав пакета Sun Studio, по умолчанию располагается в /opt/SUNWspro/bin.В дальнейших примерах я буду использовать сс, поэтому для удобства добавлю путь к его каталогу в переменную окружения $PATH: $ export PATH=$PATH:/opt/SUNWspro/bin Теперь можно запустить компилятор: $ cc -c -o hello.o hello.c Давайте рассмотрим подробнее эту команду. Она указывает компилятору обработать файл hello.c, поместив результаты свой работы в объектный файл hello.o. Ключ -с заставляет сс провести только компиляцию исходного кода, без проведения дальнейшей компоновки. Без этого ключа мы бы сразу получили готовый к исполнению файл. Но ведь нам хотелось бы самостоятельно пройти через все этапы сборки программы и посмотреть, что же происходит на каждом из них. Не правда ли? Ключ -o задает компилятору название объектного файла, в котором будет помещен откомпилированный код. Давайте посмотрим, что “думает” система относительно этого файла: $ file hello.o Обратите внимание на фразу “relocatable”, которая означает, что перед нами – объектный код. Это отнюдь не исполняемый файл, а некий “полуфабрикат”, который еще надо поместить в духовку, чтобы получить в итоге съестное блюдо. Давайте так и поступим: $ cc -o hello hello.o Эта команда очень похожа на предыдущую. Но разница между ними есть, и немалая. В первом случае мы указали cc выполнить компиляцию, во втором же – компоновку, в результате проведения которой должна получиться исполняемая программа. Давайте убедимся в этом: $ file hello Думаю, здесь все ясно без дополнительных разъяснений. Мы получили исполняемый (executable) файл, который теперь можно проверить в работе: $ ./hello Рассмотрим подробнее процесс компоновки, который, как я уже говорил, представляет собой финальную сборку программы из составляющих объектных модулей. Почему я говорю о них во множественном числе? Ведь в нашем примере мы имели дело только с одним объектным файлом? Дело в том, что в ходе компоновки сс самостоятельно добавил к нашей программе несколько библиотек. Для того чтобы увидеть их, нам поможет утилита ldd: $ ldd ./hello Вывод ldd означает, что в процессе компоновки нашего модуля hello.o к нему были добавлены две дополнительные библиотеки из каталога /lib: libc.so.1 и libm.so.2. Это системные библиотеки времени исполнения (runtime libraries) – они жизненно необходимы для работы нашего приложения. Итак, мы рассмотрели все необходимые шаги преобразования исходного кода на языке С в исполняемый код, одновременно ознакомившись с базовыми концепциями процессов компиляции и компоновки. В следующих главах мы усложним наши примеры с целью получения возможности постепенного приближения к реальным ситуациям сборки программ. 3. Статическая компоновка. Пришло время усложнить нашу программу. Давайте разобьем ее на части. Это стандартная процедура, повсеместно используемая программистами на практике. Она основана на старом добром принципе “Разделяй и властвуй”, смысл которого, применительно к написанию программ, заключается в том, что гораздо проще разделить проект на составляющие компоненты и работать с ними независимо, чем сопровождать громоздкий монолитный код. Мы поступим так же и разделим нашу программу на два исходных модуля: extern int greating(char*); Листинг 3.1 hello.c #include <stdio.h> Листинг 3.2 greating.c Рассмотрим введенные изменения. Функцию printf() мы вынесли в отдельный файл (greating.c) попутно с директивой #include , “обернув” ее при этом в нашу новую функцию – greating(). Так как мы ввели новую пользовательскую функцию, нам потребовалось объявить ее, чтобы не вызывать впоследствии гнева строгого компилятора: extern int greating(char*); Данное объявление из листинга 3.2 означает, что функция greating() принимает в качестве аргумента некую строку и возвращает целое число – в нашем случае это код возврата. Обратите внимание, что подобное объявление также присутствует и в листинге 3.1, где оно имеет уже совсем другое назначение. Это объявление сообщает компилятору, что несмотря на то, что вызов функции greating() присутствует в файле hello.c, код этой функции находится в другом месте. Следовательно, компилятор не должен искать этот код в текущем файле, а переложить всю дальнейшую работу по поиску “потерявшейся” функции на плечи компоновщика, который во время сборки обязан ее найти и поместить в нужное место. Но что делать, если компоновщик не сможет выполнить эту задачу? Рассмотрение подобной ситуации и является основной задачей данной главы. Но, сначала давайте откомпилируем и скомпонуем наш код. $ cc -c -o greating.o greating.c В первой команде мы откомпилировали исходный код файла greating.c в объектный код – библиотеку greating.o. На втором шаге я решил объединить компиляцию и компоновку (обратите внимании на отсутствие ключа -с) в одной команде, в ходе выполнения которой сс сперва откомпилировал hello.c, а затем вызвал компоновщик, который добавил к полученному объектному коду содержимое библиотеки greating.o. В результате получился готовый к исполнению файл: $ ./hello_static Теперь рассмотрим случай с “потерявшейся” функцией. Давайте уберем упоминание о библиотеке greating.o из команды компоновки: $ cc -o hello_static hello.c Мы видим сообщения об ошибках в работе программы ld. Это и есть компоновщик. Мы не вызывали его напрямую и не будем вызывать в дальнейшем – утилита сс прекрасно справится с этой задачей за нас. Хочу обратить на этот факт особое внимание: никогда не стоит использовать компоновщик самостоятельно (это удел опытных программистов с немалым стажем), иначе результаты могут оказаться абсолютно непредсказуемыми. В нашем случае решение проблемы известно. Мы знаем, что код требуемой функции находится в файле greating.o, так как мы собственноручно его туда и поместили. Но как быть в том случае, если мы компилируем код, написанный другим программистом, и в итоге компоновщик выдает подобного рода ошибку? Как найти нужную нам библиотеку? Подобная ситуация иногда встречается в реальной жизни. Попробуем найти выход из этого, казалось бы, безнадежного положения. Допустим, у нас есть информация только о названии каталога, в котором находятся библиотечные файлы компилируемого проекта. При этом мы не знаем точно в какой именно библиотеке располагается код искомой функции. В подобных случаях помощь нам может оказать nm – еще одна утилита из “джентльменского набора программиста”. $ for i in *.o; do echo "$i:"; /usr/ccs/bin/nm $i | grep greating; done Обратите внимание на запись вида UNDEF в файле hello_static.o, который остался в нашем рабочем каталоге после того, как мы удачно откомпилировали программу в первый раз. Эту запись разместил компилятор, пометив ее как неопределенную (undefined) в надежде, что компоновщик впоследствии найдет функцию “беглеца”. В первом примере, обнаружив эту запись, компоновщик произвел поиск нужной процедуры, последовательно перебрав все библиотеки, указанные нами в командной строке утилиты сс, и в итоге нашел ее в файле greating.o. В этом можно убедиться, посмотрев на строку с записью FUNC вывода утилиты nm. Во втором же примере поиск нужной библиотеки не дал положительных результатов, в результате чего компоновщик завершил свою работу с сообщением об ошибке. Тогда нам пришлось прибегнуть к помощи утилиты nm, которая, как оказалось, может быть полезной не только для программистов, но и системных администраторов. 4. Заголовочные файлы. Мы подошли к рассмотрению заголовочных файлов, с которыми обычно связано большинство проблем, возникающих в процессе сборки программ. Мы уже касались этой темы во второй главе, теперь же пришло время рассмотреть ее подробнее. Давайте, немного изменим наш последний пример. Если внимательно присмотреться, то одна и та же строка кода с объявлением функции greating() присутствует в обоих листингах. В будущем подобная ситуация может стать источником проблем – если я захочу впоследствии поменять это объявление, мне придется сделать это во всех без исключения файлах, его содержащих. Согласитесь, это крайне утомительная работа. Также, если я ошибусь и забуду внести изменения хотя бы в одном месте, то целостность проекта нарушится. Логичное решение такой проблемы – вынести весь повторяющийся код в отдельный файл. Именно с этой целью в языке С и существуют заголовочные файлы. Посмотрите на листинги обновленного примера: # include "greating.h" Листинг 4.1 hello.c #include <stdio.h> Листинг 4.2 greating.c extern int greating(char*); Листинг 4.2 greating . h Мы поместили объявление функции greating() в отдельный заголовочный файл (greating.h), включив его в исходный код обоих модулей с помощью директивы #include. Обратите внимание, что в данном примере я заключил название файла в кавычки, а не в угловые скобки. Почему я так поступил? Дело в том, что таким образом я пометил greating.h как пользовательский, а не системный заголовочный файл, тем самым указав препроцессору начать его поиск, начиная с текущего каталога. Если в ходе этой операции нужный файл не будет найден, препроцессор продолжит свою работу, последовательно перебирая локации из списка поиска. Какие каталоги входят в этот список, в какой очередности происходит поиск нужного файла в них? Это зависит от типа компилятора, так что мы не будем подробно останавливаться на этом вопросе. Основная наша задача сейчас - научиться добавлять в этот путь собственные значения. Итак, пришло время набрать новый код в текстовом редакторе и скомпилировать его. Но сначала, попробуем переместить greating.h в другое место, например, в каталог /tmp: $ mv greating.h /tmp Что же, нам явно удалось “спрятать” заголовочный файл. Как помочь препроцессору найти его? Для этого существует ключ -I , который добавляет новые каталоги к пути поиска заголовочных файлов. Наберите следующую команду: $ cc -c -I/tmp -o greating.o greating.c Теперь сообщение об ошибке пропало. Попробуйте дальше самостоятельно закончить компиляцию, а мне остается только отметить, что рассмотренный нами ключ можно использовать многократно, например, так: $ cc -c -I/tmp -I/usr/local/include -o greating.o greating.c 5. Динамическая компоновка. Динамические библиотеки – вот главный виновник подавляющего числа ошибок, возникающих в процессе компиляции приложений. Давайте рассмотрим наиболее распространенные тупиковые ситуации, связанные с их использованием. Для этого заменим статическую библиотеку greating.o на ее динамический вариант. Команда компиляции теперь примет следующий вид: $ cc -G -KPIC -o libgreating.so greating.c Применив при компиляции новые ключи, мы получили теперь уже не статическую, а динамическую библиотеку. Я дал ей расширение “so” , как это принято в среде программистов. Эта аббревиатура переводится как shared object – разделяемый объект. Что означает этот термин? Дело в том, что в случае использования статических библиотек их код встраивается непосредственно в код самого приложения во время компоновки. В случае же с библиотеками динамическими ситуация совсем иная: их код присоединяется к коду программы не на этапе ее сборки, а уже во время исполнения. Отвечает за этот процесс отдельный компонент операционной системы – компоновщик времени исполнения (runtime linker), который и производит компоновку “на лету” на начальном этапе запуска процесса. Использование динамических библиотек значительно упрощает работу программистам, а также несет с собой ряд прочих выгод, например: Повышается мобильность приложений. Программы и используемые ими библиотеки находятся в разных файлах. В итоге, для обновления приложения не потребуется длительной и утомительной его перекомпиляции – достаточно заменить старую версию библиотеки на новую. Значительно уменьшается размер программ за счет вынесения части их кода в динамические библиотеки. Такие программы быстрее грузятся на исполнение, а возможность совместного использования одной библиотеки несколькими приложениями одновременно уменьшает затраты на использование оперативной памяти. Отсюда и следует трактовка термина “разделяемый объект”. Теперь используем команду file для определения типа новой библиотеки: $ file libgreating.so Пока все идет как надо – мы получили динамическую библиотеку и теперь готовы продолжить процесс компиляции. Но сначала мне бы хотелось упомянуть об одной особенности. Дело в том, что в примерах со статической компоновкой мы указывали названия нужных нам библиотек утилите сс напрямую. В случае же с динамической компоновкой используется немного другая нотация: здесь применяется специальный ключ -l , вслед за которым должно следовать имя библиотеки без префикса “ lib” и завершающего расширения. Теперь наберите команду: bash-3.00$ cc -o hello_dynamic -lgreating hello.c Вот мы и столкнулись с первой проблемой – компоновщик не может найти требуемой библиотеки. Мы уже наблюдали подобную ситуацию в ходе знакомства с заголовочными файлами. Тогда проблема была решена с помощью ключа -I . В случае с компоновкой также существует специальный ключ -L, добавляющий дополнительные каталоги к пути поиска библиотек. Давайте добавим в этот путь текущий каталог: bash -3.00$ cc -o hello_dynamic -L. -lgreating hello.c Теперь компоновка прошла успешно. Попробуем запустить нашу новую программу: $ ./hello_dynamic На этот раз мы получили сообщение от компоновщика времени исполнения. Теперь уже он не знает о местонахождении динамической библиотеки. Утилита ldd подтверждает этот факт: $ ldd hello_dynamic Настало время рассмотреть переменную окружения LD_LIBRARY_PATH. Она предназначена для добавления каталогов к пути, которому следует компоновщик времени исполнения в ходе поиска динамических библиотек. Добавим текущий каталог к этому пути и проверим работоспособность нашей программы: $ export LD_LIBRARY_PATH=. Теперь библиотека найдена и приложение готово к работе. Давайте рассмотрим подробнее переменную LD_LIBRARY_PATH, уже давно являющейся причиной горячих споров среди системных администраторов. Одни из них утверждают, что эта переменная является подлинной брешью в системе безопасности операционной системы. По их мнению, нельзя давать непривилегированным пользователям возможности свободно изменять свойства рабочего окружения собственных процессов. Другие же оспаривают этот факт, заявляя, что выгоды от использования этой переменной перевешивают ее недостатки, связанные с вопросами безопасности. Лично мое мнение таково: исполняя обязанности старшего администратора системы, не стоит увлекаться переменной окружения LD_LIBRARY_PATH, гораздо надежнее и безопаснее использовать вместо нее системную утилиту crle. В то же время хотелось бы отметить, что в ряде случаев эта переменная может оказать воистину неоценимую помощь. Рассмотрим, например, ситуацию, когда администратор решил добавить к системному пути поиска библиотек каталог /usr/local/lib, набрав в консоли следующую команду: crle l/usr/local/lib В данном случае он допустил грубую ошибку, забыв применить ключ -u . В результате crle не добавила новый каталог к уже существующему пути, как изначально предполагалось, а полностью заменила его заданным в команде значением. В итоге администратор потерял возможность пользоваться основными системными утилитами, так как для их работы необходимы динамические библиотеки из каталогов /lib и /usr/lib, путь к которым был “так удачно” удален из системной конфигурации. Но основная неприятность заключается в том, что теперь администратор не может запустить и саму утилиту crle. Единственным выходом в данной ситуации является использование переменной окружения LD_LIBRARY_PATH: export LD_LIBRARY_PATH=/lib:/usr/lib Мораль этой истории: при использовании crle, будьте крайне внимательны и никогда не забывайте про ключ -u. 6. Утилита make и пакет autotools. Мы уже несколько раз компилировали и компоновали написанный нами исходный код, и каждый раз нам приходилось набирать одни и те же команды. Довольно скоро подобные монотонно повторяющиеся процедуры лично меня начинают утомлять. А теперь представьте себя на месте программистов, которым в реальной жизни приходится иметь дело с гораздо более длинными командами в консоли, размер которых подчас достигает нескольких десятков строк. Набирать все это заново каждый раз – воистину титанический труд. Конечно, можно поместить все команды в файл сценариев и таким образом попытаться упростить себе работу. Эта идея, несомненно, хороша, но только на первый взгляд. Посудите сами, допустим, некий программист работает над проектом, состоящим из десяти библиотек. Он решает внести очередные изменения в код одной из них, после чего заново запускает сборку из командного файла. В итоге компилятор обрабатывает не только обновленный модуль, но и все оставшиеся девять библиотек. И это несмотря на то, что код их не изменился, следовательно, перекомпиляция этих модулей не требовалась. Повторная компиляция в данном случае – лишь пустая трата времени и вычислительных ресурсов. Утилита make позволяет значительно уменьшить время, затрачиваемое на компиляцию и компоновку в ходе работы с проектом, выявляя его компоненты, требующие обновления, и автоматически вызывая соответствующие команды. Работа этой утилиты управляется с помощью файла с названием Makefile: hello_dynamic: libgreating.so hello.o Листинг 6.1 Makefile На первый взгляд, Makefile очень похож на файл сценариев командной оболочки, за исключением специальных секции вида цель: условия Эти записи-правила управляют работой утилиты следующим образом: сперва make строит дерево зависимостей между целевыми объектами и теми файлами, от которых они зависят. В данном случае под зависимостью понимается, что если в ходе работ над проектом программист внес изменения в файл-условие, то и целевой файл должен быть обновлен, т.е. заново откомпилирован или откомпонован. Построение дерева зависимостей является итеративной процедурой – условия сами могут являться целями относительно прочих файлов проекта. После построения дерева зависимостей, make начинает двигаться вдоль его ветвей, последовательно исполняя команды, заданные программистом в файле Makefile. Обратите внимание, что в корректно написанном управляющем файле утилиты make все команды должны иметь обязательные отступы с правого края, правила же, наоборот, должны размещаться, начиная с первой позиции в строке. В качестве отступов можно использовать как пробелы, так и знаки табуляции. Давайте посмотрим, как make строит дерево зависимостей, для этого следует запустить ее в режиме отладки: $ /usr/ccs/bin/make -d Теперь, когда наш проект был собран утилитой make , попробуем запустить ее повторно: $ /usr/ccs/bin/make -d Теперь make определила, что мы не производили никаких изменений в исходном коде, поэтому она не стала производить никаких дополнительных операций. Откуда она берет эту информацию? Просто сравнивая дату и время последнего обновления файлов проекта. Давайте убедимся в этом, “освежив” один из наших файлов: $ touch hello.c Обратите внимание, что правило для цели clean не попало в дерево зависимостей при построении нашего проекта. Это произошло потому, что make строит всего одно такое дерево. В нашем случае было построено дерево для файла hello_dynamic, так как его правило располагается в командном файле первым. Успешно откомпилировав его, make завершила свою работу, проигнорировав все оставшиеся секции. Однако существует возможность заставить make начать свою работу с любого указанного правила, независимо от его расположения в командном файле. Для этого надо добавить его имя в качестве параметра утилиты: /usr/ccs/bin/make clean Данная секция не имеет никаких условий, так как все что она делает – просто удаляет все библиотеки и конечный исполняемый файл в текущем каталоге. Я ввел это правило в Makefile для того, чтобы иметь возможность привести проект в исходное состояние, набрав всего одну команду. Секция clean является стандартной – каждый уважающий себя программист в обязательном порядке должен использовать ее при написании командных файлов make. Это же правило относится и к секции install, так хорошо знакомой многим администраторам UNIX систем. Несомненно, утилита make является неоценимым помощником как для программистов, так и системных администраторов. Но даже и она не может решить всех проблем, связанных со сборкой программных проектов. Самая главная из них – несовместимость рабочего окружения компиляции различных операционных систем и утилит программирования. В качестве примера можно привести различия между двумя компиляторами операционной системы solaris: cc и gcc. На первый взгляд – обе утилиты во многом схожи между собой, поэтому примеры данной статьи, ориентированные изначально на использование утилит из пакета Sun Studio, прекрасно работают и в случае использования их GNU аналогов. Для этого достаточно просто заменить вызов сс на gcc. Но есть и одно исключение в нашем случае: команда компиляции динамической библиотеки libgreating.so. Напомню, как она выглядела в пятой главе: cc -G -KPIC -o libgreating.so greating.c Компилятор gcc же в данной ситуации использует немного другой набор опций: gcc -c -fpic -o libgreating.so greating.c Несмотря на то, что из соображений совместимости утилита cc поддерживает как ключ -с, так и -fpic , существует большое количество параметров, несовместимых между версиями компиляторов различных производителей. Итак, как же поступить программисту в подобной ситуации, когда он не знает заранее, какую из имеющихся утилит предпочтет пользователь для сборки проекта? В качестве одного из возможных решений этой задачи можно предложить использование нескольких командных файлов make – каждый для собственной версии компилятора. Подобная практика была распространена несколько лет назад, до появления пакета GNU building system (иногда в компьютерной литературе можно встретить его альтернативное название – autotools), который предлагает более изящный подход к решению подобной проблемы. Принцип использования GNU building system заключается в следующем: программист формирует исходные требования к проекту, например, перечисляя необходимые заголовочные файлы и библиотеки. Затем, используя средства autotools, он формирует исполняемый файл configure, который он включает в состав дистрибутива. В ходе своего исполнения configure автоматически анализирует рабочее окружение текущей операционной системы и на основании полученных данных генерирует управляющий файл утилиты make. Таким образом, для сборки программного продукта администратору в общем случае достаточно набрать три команды: $ configure Мы не будем подробно рассматривать autotools – это отнюдь непростая тема. Тем более, крайне редко встречаются ситуации, вынуждающие администратора вносить самостоятельно собственные изменения в исходные требования, с последующей регенерацией файла configure. Поэтому мне бы хотелось только дать несколько советов по использованию утилиты configure. Совет первый: не ленитесь потратить своего времени на ознакомление с текущими опциями утилиты. Узнать их можно, вызвав configure с ключом --help. Совет второй: не забывайте про специальные переменные окружения configure, правильное использование которых может оказать немалую услугу в ходе сборки проектов. Например, в предыдущих главах мы познакомились со специальными ключами компилятора, помогающими задать ему дополнительные пути для поиска библиотек и заголовочных файлов. Но ведь это параметры компилятора, а не configure – как передать их этой утилите? Для этого существуют две специальные переменные окружения: CPPFLAFS и LDFLAGS . Давайте рассмотрим их использование на примере: CPPFLAFS="-I/usr/foo/include -I/usr/bar/include" LDFLAGS="-L/usr/foo/lib -L/usr/bar/lib" ./configure -prefix=/usr/local Также существует еще одна полезная переменная окружения: СС, принимающая в качестве параметра название компилятора. Очевидно, эта переменная предназначена для разрешения таких ситуаций, когда администратору необходимо быстро сменить используемый компилятор между последовательными сборками проекта. Совет третий: в ходе исполнения, configure ведет детальный отчет о своих действиях в файле config.log. Внимательное ознакомление с этим файлом в большинстве случаев позволяет быстро и точно выявить причину аварийного завершения утилиты. 7. Создание пакета. Итак, наш проект удачно собран – пришло время сформировать на его основе инсталляционный пакет. Но сначала необходимо определиться со структурой каталогов будущего дистрибутива. Давайте разместим программу hello_dynamic в каталоге /usr/local/bin, а библиотеку libgreating.so в /usr/local/lib. Для этого сначала создадим специальный каталог для сборки пакета c подкаталогами bin и lib, а затем переместим в них файлы нашего проекта: $ mkdir -p pkg/bin Первым шагом в создании нашего пакета является написание файла pkginfo, в котором содержится основная информация о проекте: PKG=TESThello Листинг 7.1 pkginfo Я думаю, здесь нет необходимости объяснять смысл каждой записи данного файла – их названия достаточно красноречиво говорят сами за себя. Исключение составляет разве что параметр CLASSES, но я не буду сейчас подробно останавливаться на его описании, так как классы редко используются при создании пакетов. Наш случай не является исключением из этого правила, поэтому в качестве значения параметра CLASSES я указал “none”. Позволю себе также заметить, что если пакет собирался для x86 систем, значение параметра ARCH следует поменять на x86. Теперь, в качестве второго шага, нам необходимо создать файл с названием prototype, содержащий список компонентов нашего дистрибутива. Для этого следует использовать утилиту pkgproto: $ cd pkg Программа pkgproto требует указания каталога, в котором располагаются файлы, входящие в состав будущего пакета. В данном примере я указал текущий каталог, а вывод утилиты перенаправил в файл prototype: $ cat prototype Перед тем, как начать объяснение структуры этого файла, я сперва проведу в нем необходимые изменения. В итоге, окончательная версия prototype должна выглядеть так: i pkginfo Листинг 7.2 prototype Теперь давайте рассмотрим общую структуру файла prototype. Он состоит из нескольких полей. В первом поле располагается аббревиатура, задающая тип компонента пакета: i – служебный файл пакета, d – каталог файловой системы, будет создан во время установки дистрибутива, f – регулярный файл в составе пакета. Второе поле задает класс компонента. Так как мы не используем классы, мы оставили значение по умолчанию. Третье поле задает имя файла или каталога. Обратите внимание, что в нашем случае мы использовали относительные имена, к которым во время установки пакета будет добавлен базовый префикс, указанный нами в атрибуте BASEDIR файла pkginfo. Таким образом, в нашем примере название bin/hello_dynamic будет расширено до /usr/local/bin/hello_dynamic. Мы также можем использовать и абсолютные имена файлов. Часто такая возможность возникает, если в пакете присутствует файл, который необходимо разместить за пределами базового каталога пакета. Вот пример такой записи: f none /var/svc/manifest/network/named.xml=named.xml 0644 root sys Как видно из этого примера, формат имени компонента в таком случае немного иной, чем тот, что мы используем в нашем проекте. Данная запись означает, что файл пакета с названием named.xml во время инсталляции должен быть помещен в каталог /var/svc/manifest/network. Остальные поля файла pkginfo задают права, пользователя-владельца и группу-владельца объекта файловой системы соответственно. Эти атрибуты будут назначены файлу или каталогу в процессе установки пакета. Знаки вопроса в этих полях означают, что инсталлятор не будет менять соответствующие атрибуты объекта в том случае, если он уже существует. В обратном случае система задаст их самостоятельно во время инсталляции. Обратите внимание, что я удалил все свойства файла pkginfo – этот файл является специальным, поэтому система автоматически выберет для него нужные параметры. Также я удалил запись о файле prototype за ее ненадобностью, так как он используется только во время формирования пакета и в финальной версии дистрибутива не присутствует. Теперь все готово к окончательной сборке пакета: $ pkgmk -r . -d . -o Здесь первый параметр утилиты pkgmk указывает ей каталог с исходными файлами пакета, второй – место, куда она должна поместить собранный дистрибутив. Если опустить ключ -d, то по умолчанию утилита разместит готовый пакет в каталоге /var/spool/pkg. Параметр -o следует использовать при повторной сборке, он заставляет pkgmk предварительно удалить старую версию пакета. В нашем случае, когда мы собирали дистрибутив в первый раз, в нем не было особой необходимости, но все же я решил поместить его в пример в учебных целях. Нас можно поздравить с очередной победой – наш первый пакет готов, теперь его можно установить с помощью команды pkgadd. Обратные ссылки
URI этой записи для создания обратных ссылок (trackback)
Нет обратных ссылок
Комментарии
Показывать комментарии
(Как список | Древовидной структурой)
А я вот всё таки скажу спасибо. а то вы всё пишете как это круто сборка из исходников и сразу лезете в дебри какие нить. а тут хоть основы почитать можно
Спасибо за статью, все очень здорово разложено. Есть один вопрос, в способе описанном тут пакет получается в виде каталога, а как сделать его в виде одного файла?
|
КатегорииБыстрый поискКалендарь
Новостные лентыАвторы |
|||||||||||||||||||||||||||||||||||||||||||||||||
|
|



