Внутреннее устройство Linux - Уорд Брайан. Страница 105
Никогда не определяйте переменную LD_LIBRARY_PATH в файлах запуска оболочки или при компиляции программ. Когда динамический компоновщик встречает эту переменную, ему зачастую приходится просматривать содержимое каждого указанного каталога большее число раз, чем вы могли себе представить. Это сильно сказывается на быстродействии, но, что более важно, могут возникнуть конфликты и несоответствия библиотек, поскольку компоновщик времени исполнения просматривает эти каталоги для каждой программы.
Если вы должны использовать переменную LD_LIBRARY_PATH, чтобы запустить какую-либо программу, для которой у вас нет исходного кода (или приложение, которое вы предпочли бы не компилировать, вроде Mozilla или каких-либо других), применяйте сценарий обертки. Допустим, исполняемому файлу /opt/crummy/bin/crummy.bin необходимы совместно используемые библиотеки из каталога /opt/crummy/lib. Напишите сценарий обертки с именем crummy следующим образом:
#!/bin/sh
LD_LIBRARY_PATH=/opt/crummy/lib
export LD_LIBRARY_PATH
exec /opt/crummy/bin/crummy.bin $@
Если избегать переменной LD_LIBRARY_PATH, то можно предотвратить большинство проблем с совместно используемыми библиотеками. Иногда возникает еще одна существенная проблема для разработчиков: интерфейс прикладного программирования (API) для какой-либо библиотеки может немного измениться при переходе от одной младшей версии к другой, это нарушит работу установленных программ. Лучшие решения проблемы являются профилактическими: либо пользуйтесь последовательной методологией, чтобы установить совместно используемые библиотеки с помощью команды -Wl,-rpath для создания ссылки на путь времени исполнения, либо просто применяйте статические версии непонятных библиотек.
15.2. Утилита make
Программа, у которой есть несколько файлов исходного кода или для которой необходимы необычные параметры компиляции, слишком неудобна для компиляции вручную. Эта проблема возникает на протяжении многих лет, и традиционным средством Unix для управления компиляцией является утилита make. Вам следует узнать немного об этой утилите, если вы работаете в системе Unix, поскольку системные утилиты иногда опираются на нее в своей работе. Однако данная глава является лишь верхушкой айсберга. Утилите make посвящены целые книги, например Managing Projects with GNU Make («Управление проектами с помощью утилиты GNU Make») Роберта Мекленбурга (Robert Mecklenburg) (O’Reilly, 2004). Кроме того, большинство пакетов Linux собрано с использованием дополнительного уровня, основанного на утилите make или подобном средстве. Есть множество систем для сборки; одну из них с названием Autotools мы рассмотрим в главе 16. Утилита make является большой системой, но получить представление о том, как она работает, совсем не трудно. Когда вы увидите файл с именем Makefile или makefile, знайте, что вы имеете дело с утилитой make. Попробуйте запустить команду make, чтобы понять, можно ли что-нибудь собрать.
Главной идеей утилиты make является цель — то, чего вы желаете достичь. Целью может быть файл (файл .o, исполняемый файл и т. д.) или ярлык. Кроме того, некоторые цели зависят от других целей; например, вам необходимо создать полный набор файлов .o, прежде чем вы сможете скомпоновать исполняемый файл. Эти требования называются зависимостями.
Чтобы собрать цель, утилита make следует какому-либо правилу, например, определяющему, как перейти от исходного файла .c к объектному файлу .o. Утилите make уже известны некоторые правила, но вы можете изменить их, а также создать собственные.
15.2.1. Пример файл Makefile
Следующий очень простой файл Makefile собирает программу myprog из файлов aux.c и main.c:
# object files
OBJS=aux.o main.o
all: myprog
myprog: $(OBJS)
$(CC) -o myprog $(OBJS)
Символ # в первой строке этого файла означает комментарий.
Следующая строка является всего лишь макроопределением; она задает для переменной OBJS два имени объектных файлов. Это будет важно в дальнейшем. Сейчас обратите внимание на то, как записывается макроопределение и как на него ссылаются далее ( ($(OBJS) ).
Следующий элемент файла Makefile содержит первую цель, all. Первая цель всегда является целью по умолчанию, утилита make будет собирать ее, если вы запустите команду make в командной строке саму по себе.
Правило сборки цели следует после двоеточия. Для цели all в этом файле Makefile сказано, что вам необходимо удовлетворить чему-то по имени myprog. Это первая зависимость в данном файле; цель all зависит от myprog. Заметьте, что myprog может быть реальным файлом или целью другого правила. В данном случае оно является и тем и другим (правилом для цели all и целью для OBJS).
Чтобы собрать программу myprog, этот файл Makefile использует макроопределение $(OBJS) в зависимостях. Макроопределение развертывается в имена aux.o и main.o, поэтому программа myprog зависит от этих двух файлов (они должны быть реальными файлами, поскольку нигде в файле Makefile нет целей с такими именами).
Данный файл Makefile предполагает, что у вас есть два файла с исходным кодом на языке C: aux.c и main.c в одном каталоге. Если запустить утилиту make для файла Makefile, то в результате будут показаны команды, выполняемые утилитой:
$ make
cc -c -o aux.o aux.c
cc -c -o main.o main.c
cc -o myprog aux.o main.o
Схема зависимостей приведена на рис. 15.1.
Рис. 15.1. Зависимости в файле Makefile
15.2.2. Встроенные правила
Каким же образом утилита make узнает о том, как перейти от файла aux.c к файлу aux.o? Ведь файла aux.c нет внутри файла Makefile. Ответ такой: утилита make следует встроенным правилам. Она знает о том, что следует искать файл .c, если вам необходим файл .o, и, более того, она знает, как запустить команду cc -c для этого файла .c, чтобы добиться цели — создать файл .o.
15.2.3. Окончательная сборка программы
Последний шаг при получении программы myprog довольно хитрый, но идея достаточно ясная. Когда у вас появятся два объектных файла в макроопределении $(OBJS), можно запустить компилятор C в соответствии со следующей строкой (здесь переменная $(CC) развертывается в имя компилятора):
$(CC) -o myprog $(OBJS)
Отступ перед переменной $(CC) является табуляцией. Вы обязаны вставлять табуляцию перед каждой реальной командой в той строке, где она находится.
Остерегайтесь следующего сообщения:
Makefile:7: *** missing separator. Stop.
Подобная ошибка означает, что файл Makefile не в порядке. Табуляция является разделителем, и, если она отсутствует или есть какая-либо другая помеха, вы увидите такую ошибку.
15.2.4. Поддержание актуальных версий файлов
Одним из последних фундаментальных свойств утилиты make является то, что ее цели должны соответствовать по времени зависимостям. Если вы дважды вызовете утилиту make из предыдущего примера, первая из них соберет программу myprog, но вторая выдаст такое сообщение:
make: Nothing to be done for 'all'.
Во второй раз утилита make посмотрела свои правила и обнаружила, что программа myprog уже существует, поэтому она не стала повторно собирать ее, поскольку ни одна из зависимостей не изменилась с тех пор, как была собрана программа. Чтобы поэкспериментировать, выполните следующее.
1. Запустите команду touch aux.c.
2. Запустите утилиту make еще раз. На этот раз она определит, что файл aux.c является более «свежим», чем файл aux.o, уже находящийся в каталоге, поэтому она скомпилирует файл aux.o заново.
3. Программа myprog зависит от файла aux.o, и теперь файл aux.o является более новым, чем уже существующая программа myprog, поэтому утилита make должна создать программу myprog заново.