Содержание


Отладка работы утилиты make

Советы и рекомендации о том, как заставить make работать на вас, а не против вас

Comments

Сборка большинства программ для UNIX® и Linux® производится путем запуска утилиты make. Эта утилита считывает файл (обычно носящий имя "makefile" или "Makefile", здесь он называется просто "makefile"), в котором содержатся инструкции, и выполняет различные действия, необходимые для сборки программы. Во многих случаях сам makefile полностью генерируется специальной программой; например, для разработки процедур сборки используются программы autoconf/automake. В иных программах может потребоваться изменение файла makefile напрямую, и, конечно же, вам придется написать такой файл при разработке новой программы.

Фраза "утилита make" является ошибочной. Существует как минимум три различных наиболее распространенных варианта этой утилиты: GNU make, System V make и Berkeley make. Каждый из них вырос из базовой спецификации времен начала становления UNIX и в каждом из них добавлены новые возможности. Это привело к сложной ситуации: некоторые наиболее часто используемые функции, например, включение в makefile других файлов по ссылке, перестали быть портируемыми! Единственным решением является написание программы, которая создает файлы makefile. Поскольку утилита GNU make бесплатна и широко распространена, некоторые разработчики просто кодируют для нее; кроме того, некоторые проекты, берущие свои корни из BSD, потребуют от вас использования Berkeley make (которая также бесплатна).

Менее распространена, но всё ещё значима утилита smake Йорга Шиллинга (Jörg Schilling), а также отсутствующий пятый член семейства, первоначальная утилита make, определяющая комплекс основных функций, общих для всех остальных утилит. Несмотря на то, что утилита smake не используется по умолчанию на всех системах, это хорошая реализация make, и она является предпочтительной в некоторых программах (особенно в написанных Шиллингом).

Давайте рассмотрим некоторые из наиболее распространенных проблем, с которыми вы можете встретиться при работе с файлами makefile.

Определение файла makefile

Для того чтобы выполнить отладку работы утилиты make, вы должны уметь читать содержимое файла makefile. Как вы уже знаете, назначением файла makefile является предоставление инструкций программе сборки. Одной из ключевых возможностей утилиты make является управление зависимостями: make пытается перестроить только то, что изменилось при обновлении программы. В общем, это выражается рядом правил зависимости. Такое правило выглядит следующим образом:

Листинг 1. Вид правила зависимости
target: dependencies
	instructions

В этой конструкции видна, вернее - не видна, основная проблема, с которой люди встречаются при написании своего первого файла. Отступ выполняется знаком табуляции. Он не содержит ни одного пробела. Сообщение об ошибке, выдаваемое Berkeley make для файла, в котором использованы пробелы, не очень информативно:

Листинг 2. Сообщение об ошибке Berkeley make
make: "Makefile" line 2: Need an operator
make: Fatal errors encountered -- cannot continue

Утилита GNU make, хотя и не может обработать файл, дает более полезный совет:

Листинг 3. Сообщение об ошибке GNU make
Makefile::2: *** missing separator (did you mean TAB instead of 8 spaces?).  Stop.

Следует обратить внимание, что зависимости и команды необязательны; необходимо указывать лишь цель и двоеточие. Итак, мы поговорили о синтаксисе. Какова же семантика? Семантика такова, что если вы желаете провести сборку цели, будет проведена проверка всех зависимостей. Фактически, утилита будет рекурсивно пытаться собрать их; если у зависимости, в свою очередь, есть ещё одна зависимость, она будет обработана до того, как будет продолжена обработка правила. Если цель target: существует и ее дата не является более ранней, чем даты всех объектов, перечисленных в пункте зависимости, ничего не выполняется. Если target не существует или одна или несколько зависимостей обновились, утилита make выполнит команды instructions. Зависимости обрабатываются в порядке указания. Если зависимости не указаны, команды будут выполнены в безусловном порядке. Зависимости также называются источниками.

Если в командной строке указана цель (например, make foo), то утилита make будет пытаться построить эту цель. В противном случае она будет пытаться построить первую цель, указанную в файле. Некоторые разработчики берут за правило указывать первую цель следующим образом:

Листинг 4. Наиболее часто используемое условное указание первой цели
default: all

Некоторые предполагают, что утилита make использует это правило потому, что оно называется "default" ("по умолчанию"). Это не так; оно используется потому, что это первое правило в файле. Вы можете дать ему любое название, какой только пожелаете, но название "default" хорошо тем, что оно несет в себе смысловую нагрузку для читающего код. Помните о том, что создаваемый вами файл makefile будут читать не только программы make, но и люди.

Фиктивные цели

В общем случае предполагается, что функция цели заключается в создании файла из других файлов. В действительности это не всегда так. В большинстве файлов makefile есть хотя бы пара правил, которые никогда не создадут цель. Рассмотрим как пример следующее правило:

Листинг 5. Пример фиктивной цели
all: hello goodbye fibonacci

Это дает утилите make следующие инструкции -- если она желает собрать цель all, сначала необходимо убедиться в том, что существуют актуальные версии "hello", "goodbye" и "fibonacci". Тогда... ничего не делать. Никаких команд не дается. После выполнения этого правила не создается файла с названием "all". Это фиктивная цель. В некоторых версиях утилиты make используется технический термин "фиктивная" ("phony") цель.

Фиктивные цели используются для организационных нужд и являются отличным средством создания понятного и удобочитаемого файла makefile. Например, часто можно увидеть правило, похожее на это:

Листинг 6. Разумное использование фиктивной цели
build: clean all install

Здесь указывается порядок операций процесса сборки.

Специальные цели и источники

Определен ряд специальных целей, оказывающих особое влияние на утилиту make и предоставляющих средства настройки. Точный перечень изменяется от одной реализации к другой; наиболее часто используется цель .SUFFIXES, "источником" для которой является ряд шаблонов, добавляемых к списку найденных расширений (суффиксов) файлов. Специальные цели не учитываются в обычном правиле, гласящем, что make по умолчанию строит первую цель в файле makefile.

В некоторых версиях утилита make позволяет определять специальные цели наряду с зависимостями для данной цели, например, .IGNORE, что обозначает, что ошибки команды, используемой для сборки этой цели, будут проигнорированы, как если бы перед ними стоял знак комментария. Эти флаги не всегда портируемы, но знать их для отладки файла makefile необходимо.

Общие правила

В make существуют неявные правила выполнения общих преобразований на основе расширений файлов. Например, при отсутствии файла makefile создайте файл с названием "hello.c" и выполните команду make hello:

Листинг 7. Пример неявных правил для файлов C
$ make hello
cc -O2   -o hello hello.c

В файлах makefile для более крупных программ можно просто указать перечень необходимых модулей объектов (hello.o, world.o и так далее), после чего указать правило преобразования файлов .c в файлы .o:

Листинг 8. Правило преобразования файлов .c в файлы .o
.c.o:
	cc $(CFLAGS) -c $<

На самом деле в большинстве утилит make уже есть правило, очень похожее на это; если вы затребуете у make построить файл file.o и в наличии будет соответствующий файл file.c, утилита сработает как положено. Сочетание "$<" представляет собой специальную предварительно определенную переменную утилиты make, которая указывает на источник правила. Таким образом, мы подошли к понятию переменных make.

Общие правила основаны на декларировании "суффиксов" файлов, которые рассматриваются как расширения файлов, а не как часть имени.

Переменные

Программа make использует переменные для облегчения повторного использования общих значений. Вероятно, наиболее часто используемое значение - CFLAGS. Следует уточнить несколько моментов, касающихся переменных утилиты make. Эти переменные не обязательно являются переменными среды. Если с данным именем не связано никаких переменных make, утилита проверит переменные среды; однако это не означает, что переменные make экспортируются в среду. Порядок старшинства не установлен, в общем случае порядок от старшего к младшему определяется следующим образом:

  1. Настройки переменных в командной строке
  2. Переменные, установленные в файле makefile родительского процесса make
  3. Переменные, установленные в файле makefile данного процесса make
  4. Переменные среды

Таким образом, переменные среды используются в том случае, если переменная не установлена ни в одном из файлов makefile, ни в командной строке. (Примечание: переменные родительского makefile иногда, но не всегда, передаются дочернему процессу. Правила, как можно догадаться, меняются от одной реализации make к другой.)

Распространенной проблемой среди людей, работающих с make, является необъяснима замена частей переменных: например, $CFLAGS заменяется на "FLAGS". Для того, чтобы указать переменную make, заключите её наименование в скобки: $(CFLAGS). В противном случае вы получите переменную $C, за которой будет следовать текст FLAGS.

У некоторых переменных есть специальные значения, зависящие от правил, в которых они используются. Среди наиболее часто используемых:

  • $< - Источник, из которого должна быть изготовлена цель
  • $* - Базовое имя цели (без расширения и имени каталога)
  • $@ - Полное имя цели

Авторы Berkeley make выводят эти переменные из обращения, но пока они всё ещё портируемы. Во всяком случае, портируемы отчасти; точные определения в различных реализациях утилиты make могут различаться. Любая сложная программа, написанная с их помощью, скорее всего, будет ограничена конкретной реализацией.

Сценарии командного процессора

Иногда бывает желательно выполнять задачи определенного рода за пределами портируемости утилиты make. Традиционным решением является написание встроенного сценария командного процессора (shell), поскольку make выполняется именно через него. Рассмотрим, как это работает.

Во-первых, необходимо помнить, что, несмотря на то, что обычно сценарии командного процессора пишутся в несколько строк, их можно сжать в одну строку, разделяя элементы точкой с запятой. Во-вторых, нужно отдавать себе отчет в том, что это нечитаемо. Можно найти компромисс: писать сценарии с обычными отступами, но заканчивая каждую строку "; \". Такое завершение синтаксически (точкой с запятой) разделяет команды процессора, но в то же время составляет текстовую часть одной команды make, которая передается командному процессору целиком. Например, в файле makefile верхнего уровня могут содержаться следующие строки:

Листинг 9. Разрыв строк в сценарии командного процессора
all:
	for i in $(ALLDIRS) ; \
	do      ( cd $$i ; $(MAKE) all ) ; \
	done

Здесь показаны три вещи, которые всегда необходимо помнить. Во-первых, используйте точки с запятой и обратные слэши. Во-вторых, для создания переменных используйте $(VARIABLE). В-третьих, используйте $$ для передачи сценарию знака доллара. Вот и всё! Это действительно просто.

Префиксы

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

Если вы не желаете, чтобы команда выводилась на экран, начните ее со знака @. Этот вариант чаще всего используется для вывода сообщений:

Листинг 10. Подавление вывода
all:
	@echo "Beginning build at:"
	@date
	@echo "--------"

Без знака @ эти инструкции приведут к выводу следующей информации:

Листинг 11. Команды без знака @
echo "Beginning build at:"
Beginning build at:
date
Sun Jun 18 01:13:21 CDT 2006
echo "--------"
--------

Несмотря на то, что символ @ фактически не влияет на действия, выполняемые утилитой make, он остается очень популярной функцией.

Действия, которые нельзя сделать портируемыми

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

Включение файлов

Одной из наиболее удручающих проблем совместимости долгое время является обработка включений в файлы makefile. Ранние реализации утилиты make, в отличие от современных, не всегда предоставляли пути для выполнения этой операции. Синтаксис GNU make - простая команда include file. Традиционный синтаксис Berkeley - .include "file". На сегодняшний день по крайней мере одна реализация Berkeley также поддерживает нотацию GNU, однако это делают не все. Портируемое решение, найденное autoconf и Imake, состоит в простом включении определения каждой переменной, которая, по вашему мнению, может вам понадобиться.

Некоторые программы просто ставят условием использование GNU make. Другим необходима Berkeley make. Третьим - smake. Если вам очень нужно включать файлы, нет ничего немыслимого в том, чтобы просто указать, с помощью какой утилиты make будет строиться ваше дерево файлов. (Из трех портируемых утилит с открытым исходным кодом я предпочитаю Berkeley make.)

Получение переменных во вложенных сборках

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

В зависимости используемой вами версии утилиты make довольно неплохое решение состоит в том, чтобы определять переменные в каждом файле только при условии, что они еще не установлены; поэтому в случае полной сборки изменения, сделанные на верхнем уровне, будут влиять также и на подкаталоги. Конечно же, если зайти в подкаталог и выполнить утилиту make там, вы получите другие, несовместимые результаты...

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

Некоторые авторы пропагандируют более простое решение: вообще не использовать утилиту make рекурсивно. Для большинства проектов это подходящее решение, которое может значительно упростить (и ускорить!) компиляцию. Вероятно, каноническим источником можно считать статью Питера Миллера (Peter Miller), "Рекурсивное использование Make вредно" (см. Ссылки).

Что делать в случае возникновения проблем

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

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

Если не удалось найти программу, проверьте, установлена ли она. Если это так, проверьте пути; у некоторых разработчиков есть привычка указывать в файлах makefile абсолютные пути, что может привести к возникновению ошибок на других системах. Если вы производите установку в папку /opt, а в makefile указаны ссылки на /usr/local/bin, сборка завершится ошибкой. Исправьте путь.

Проверьте ваши часы; более того, посмотрите на даты создания файлов в дереве сборки и сравните их с датами создания других файлов вашей системы и вашими часами. Поведение утилиты make при подаче на вход хронологически противоречивых данных может варьировать от безобидного до сюрреалистического. Если у вас есть проблемы с часами (например, у некоторых "новых" файлов установлена дата создания в 1970 году), вам придется исправить их. Вам поможет утилита "touch". Сообщения, которые вы получаете при разнице во времени, бывают весьма неочевидными.

Если сообщение об ошибке уведомляет вас о синтаксической ошибке или о том, что не установлено или установлено неправильно множество переменных, попробуйте использовать другую версию make. Например, некоторые программы выдают загадочные ошибки при работе с gmake, но прекрасно собираются с помощью smake. Совсем таинственные ошибки могут означать, что вы используете с GNU make файл makefile, подготовленный для Berkeley, или наоборот. Программы, изначально написанные для Linux, часто предполагают наличие GNU make и выдают ошибку в самых неожиданных местах, если встречают что-либо иное, даже если в документации об этом не сказано ни слова.

В этом случае будут крайне полезны отладочные флаги. Используя в GNU make флаг -d, вы получите ОГРОМНЫЙ объем информации, в том числе полезной. В Berkeley make флаг -d имеет несколько дополнительных флагов; -d A представляет полное множество, можно использовать отдельные подмножества; например, -d vx выдаст отладочную информацию о назначении переменных (v), при этом все команды будут выполняться с ключом sh -x, чтобы командный процессор выводил все команды точно так, как он их получает. Отладочный флаг -n приводит к выводу утилитой make перечня действий, которые она считает необходимым выполнить; он не всегда верен, но часто наводит на мысли о том, где именно кроется ошибка.

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

Документация

Как часто бывает, основная документация к GNU make, к сожалению, не доступна в формате man; вместо этого вам придется использовать систему info, и у вас не получится просто набрать man make и посмотреть интересующую вас информацию. Как бы то ни было, документация достаточно полна.

Крайне сложно получить хорошую документацию по "безопасному набору" функций, которые поддерживаются во всех реализациях. Документация к Berkeley make и GNU make обычно описывает добавленные расширения, однако перед тем как полагаться на какие бы то ни было конкретные утверждения, бывает полезно их проверить.

Развивающееся со временем расхождение между версиями BSD привело к возникновению едва уловимых отличий в реализации утилиты make. Из трех этих версий наиболее активно поддерживается на других системах утилита make из NetBSD; система NetBSD pkgsrc используется на других платформах и существенно опирается на реализацию make NetBSD.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=188805
ArticleTitle=Отладка работы утилиты make
publish-date=01172007