Вам нужен файл, называемый make-файлом, чтобы указать программе make, что делать. Чаще всего, make-файл указывает, как компилировать и компоновать программу.
В этой главе мы обсудим простой make-файл, который описывает, как компилировать и компоновать тектовый редактор, состоящий из восьми исходных C-файлов и трех заголовочных файлов. Этот make-файл может также указывать программе make, как выполнить различные команды, если явно указан запрос на их исполнение (например, удалить определенные файлы в качестве команды clean). Более сложный пример make-файла можно увидеть в приложении Б [Сложный make-файл].
Когда make перекомпилирует редактор, каждый измененный исходный C-файл должен быть перекомпилирован. Если был изменен заголовочный файл, на всякий случай нужно перекомпилировать каждый исходный C-файл, который его включает. Каждая компиляция порождает объектный файл, соответствующий исходному файлу. Наконец, если какой-либо исходный файл был перекомпилирован, все объектные файлы, как новые, так и оставшиеся от предыдущих компиляций, должны быть скомпонованы вместе для создания нового исполняемого файла редактора.
Простой make-файл состоит из "правил" следующего вида:
ЦЕЛЬ ... : ЗАВИСИМОСТЬ ...
КОМАНДА
...
...
>
ЦЕЛЬ обычно представляет собой имя файла, генерируемого программой
make; примерами целей являются исполняемые или объектные файлы. Цель
может также быть именем выполняемого действия, как, например, 'clean'
(смотрите раздел 4.4 [Цели-имена действий]).
ЗАВИСИМОСТЬ - это файл, используемый как вход для порождения цели. Часто цель зависит от нескольких файлов.
КОМАНДА - это действие, которое выполняет make. Правило может иметь более, чем одну команду - каждую на своей собственной строке. Важное замечание: вы должны начинать каждую строку, содержащую команды, с символа табуляции. Это является незаметным средством борьбы с неострожностью.
Обычно команда появляется в правиле с зависимостями и служит для создания целевого файла, если какая-либо из зависимостей изменилась. Однако, правило, определяющее команды для цели, не обязательно должно иметь зависимости. Например, правило, содержащее команду удаления, связанную с целью 'clean', не имеет зависимостей.
Правила описывают, как и когда заново порождать определенные файлы, которые являются целями правил. Правило может также описывать, как и когда выполнять действие. Смотрите раздел 4 [Написание правил].
Помимо правил, make-файл, может содержать другой текст, однако простой make-файл содержит только правила. Правила могут выглядеть более сложными, чем показаный шаблон, но все они более или менее соответствуют ему по структуре.
Вот простой make-файл, который описывает, как исполняемый файл, называемый edit, зависит от восьми объектных файлов, которые, в свою очередь, зависят от восьми исходных C-файлов и трех заголовочных файлов.
В этом примере все C-файлы включают 'defs.h', но файл 'command.h' включают только те, которые определяют команды редактирования, а файл 'buffer.h' - только файлы низкого уровня, изменяющие буфер редактирования.
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
Мы разбиваем каждую длинную строку на две строки, используя
обратную косую черту, за которой следует перевод строки; это аналогично
использованию одной длинной строки, но легче для чтения.
Чтобы использовать этот make-файл для создания исполняемого файла с именем 'edit', наберите в командной строке:
make
Чтобы использовать этот make-файл для удаления исполняемого файла и всех объектных файлов из текущего каталога, наберите в командной строке:
make clean
В приведенном примере make-файла к целям относятся, в частности, исполняемый файл 'edit' и объектные файлы 'main.o' и 'kbd.o'. К зависимостям относятся такие файлы, как 'main.c' и 'defs.h'. Фактически, каждый объектный файл является как целью, так и зависимостью. Примерами команд являются 'cc -c main.c' и 'cc -c kbd.c'.
Когда цель является файлом, этот файл должен быть перекомпилирован или перекомпонован, если изменилась любая из его зависимостей. Кроме того, любые зависимости, которые сами автоматически генерируются, должны обновляться первыми. В этом примере, 'edit' зависит от каждого из девяти объектных файлов; объектный файл 'main.o' зависит от исходного файла 'main.c' и заголовочного файла 'defs.h'.
За каждой строкой, содержащей цель и зависимости, следует команда. Эти команды указывают, как обновлять целевой файл. В начале каждой командой строки должен располагаться символ табуляции, чтобы отличать командные строки от других строк make-файла. (Держите в уме, что make ничего не знает о том, как работают команды. Обеспечить команды, которые корректно обновят целевой файл - целиком ваша забота. Все, что делает make - это выполнение команд из опреденного вами правила, когда целевой файл должен быть обновлен.)
Цель 'clean' является не файлом, а просто именем действия. Так как обычно вы не хотите выполнять действия из этого правила, 'clean' не является зависимостью какого-либо другого правила. Следовательно, make никогда ничего с ним не сделает, если вы этого специально не укажете. Обратите внимание, что это не только не является зависимостью, оно также не имеет никаких зависимостей, таким образом, единственным предназначением правила является выполнение определенных в правиле команд. Цели, которые не указывают на файлы, а являются просто действиями, называются целями-именами действий. Смотрите раздел 4.4 [Цели-имена действий] для информации об этой разновидности целей. Смотрите раздел 5.4 [Ошибки в командах], где показывается, как заставить make игнорировать ошибки от rm и любых других команд.
По умолчанию, make начинает с первого правила (не считая правил, имена целей у которых начинаются с '.'). Это называется главной целью по умолчанию. ( Главной целью называется цель, обновление которой является изначальной задачей программы make. Смотрите раздел 9.2 [Аргументы для определения главных целей].)
В простом примере из предыдущего раздела главной целью по умолчанию является обновление исполняемой программы 'edit'; следовательно, мы располагаем это правило первым.
Таким образом, когда вы даете команду:
make
make читает make-файл в текущем каталоге и начинает с обработки первого
правила. В приведенном примере им является правило для перекомпоновки
'edit'; но, прежде чем make сможет полностью обработать это правило, он
должен обработать правила для файлов, от которых зависит 'edit' (в
данном случае ими являются объектные файлы). Каждый из этих файлов
обрабатывается в соответствии со своими собственным правилом. Эти
правила указывают обновить каждый объектный файл путем компиляции его
исходного файла. Перекомпиляция должна быть проведена, если исходный
файл или любой из заголовочных файлов, упомянутых среди зависимостей,
обновлен позднее, чем объектный файл, или если объектный файл не
существует.
Другие правила обрабатываются по той причине, что их цели появляются в качестве зависимостей главной цели. Если от какого-либо правила не зависит главная цель (или что-нибудь, отчего она зависит), то это правило не обрабатывается, если вы не укажете программе make сделать это (с помощью такой команды, как make clean).
Перед перекомпиляцией объектного файла make рассматривает время обновления его зависимостей: исходного файла и заголовочных файлов. Данный make-файл не определяет ничего, что должно делаться для их порождения - файлы, имена которых оканчиваются на '.c' и '.h' не являются целями каких-либо правил - таким образом, make ничего не делает для этих файлов. Однако make мог бы обновить автоматически генерируемые C-программы, например, получаемые с помощью программ Bison или Yacc, если бы для них, в этом случае, были определены свои правила.
После перекомпиляции всех объектных файлов, для которых это необходимо, make решает, перекомпоновывать ли 'edit'. Это должно быть сделано, если файл 'edit' не существует, или какой-либо из объектных файлов обновлен позднее его. Если объектный файл был только что перекомпилирован, то сейчас он новее, чем 'edit', так что 'edit' перекомпоновывается.
Таким образом, если мы изменим файл 'insert.c' и запустим make, make откомпилирует этот файл для обновления 'insert.o', и затем скомпонует 'edit'. Если мы изменим файл 'command.h' и запустим make, make перекомпилирует объектные файлы 'kbd.o', 'command.o', и 'files.o', а затем скомпонует 'edit'.
В нашем примере мы вынуждены были дважды перечислять все объектные файлы в правиле для 'edit':
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
Такое дублирование способствует появлению ошибок - если в систему
добавляется новый объектный файл, мы можем добавить его в один список и
забыть про другой. Мы можем устранить риск и упростить make-файл при
помощи использования переменных. Переменная позволяет один раз
определить текстовую строку и затем подставлять ее во многих местах
(смотрите главу 6 [Как использовать переменные]).
Для любого make-файла стандортной практикой является наличие переменной, называемой objects, OBJECTS, objs, OBJS, obj или OBJ, которая представляет собой список имен всех объектных файлов. Мы могли бы определить такую переменную objects со значением, являющимся списком объектных файлов из приведенного make-файла:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
Тогда везде, где мы хотим поместить список имен объектных файлов, мы
можем подставить значение переменной, написав '$(objects)' (смотрите
главу 6 [Как использовать переменные]).
Вот как полностью выглядит make-файл, когда вы используете переменную для объектных файлов:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
Писать команды для компиляции отдельных исходных файлов не является необходимым, поскольку make может сам их определить: он имеет неявное правило для обновления '.o'-файла из соответствующего '.c'-файла, используя команду 'cc -c'. Например, он будет использовать команду 'cc -c main.c -o main.o' для компиляции 'main.c' в 'main.o'. Следовательно, мы можем опустить команды из правил для объектных файлов. Смотрите главу 10 [Использование неявных правил].
Когда '.c'-файл автоматически используется таким способом, он также автоматически добавляется в список зависимостей. Поэтому мы можем опустить '.c'-файлы в зависимостях, позволяющих нам опустить команды для компиляции.
Вот пример, использующий оба этих изменения, а также переменную objects, о которой говорится выше:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
-rm edit $(objects)
Именно так нам следует писать make-файл в реальной практике. (Усложнения, связанные с 'clean', опиываются в другом месте. Смотрите раздел 4.4 [Цели-имена действий] и раздел 5.4 [Ошибки в командах].)
Неявные правила важны из-за их удобства. Вы увидите, что они используются часто.
Когда объекты make-файла создаются только при помощи правил по умолчанию, возможен альтернативный стиль построения make-файла. При его использовании вы группируете записи по их зависимостям, а не по их целям. Вот как это выглядит:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
В данном случае 'defs.h' является зависимостью для всех объектных
файлов; 'command.h' и 'buffer.h' является зависимостью для определенных
файлов, перечисленных в соответствующих правилах.
Является ли это лучшим способом - дело вкуса; этот способ более компактен, тем не менее некоторые недолюбливают его, поскольку считают более удобным располагать информацию о каждой цели в одном месте.
Вы могли бы захотеть написать правила не только для компиляции программ. Make-файлы часто указывают, как делать некоторые другие действия, отличные от компиляции: например, как удалить все объектные и исполняемые файлы в текущем каталоге (очистить каталог).
Вот как мы могли бы написать правило make для очистки нашего редактора из примера:
clean:
rm edit $(objects)
На практике, мы могли бы захотеть написать правило несколько более
сложным способом, чтобы обработать непредвидельные ситуации. Сделаем
следующее:
.PHONY : clean
clean :
-rm edit $(objects)
Это не дает программе make нарушить логику работы, когда используемый
файл называется 'clean' и заставляет ее продолжаться вопреки ошибкам со
стороны rm. ( смотрите раздел 4.4 [Цели-имена действий]) 5.4 [Ошибки в
командах]).
Правило, подобное этому, не следует размещать в начале make-файла, поскольку мы не хотим выполнять его по умолчанию! Таким образом, в примере make-файла мы хотим, чтобы правило для 'edit', которое перекомпилирует редактор, оставалось главной целью по умолчанию.
Так как 'clean' не является зависимостью 'edit', это правило вообще не будет выполняться, если вы дали команду 'make' без аргументов. Для того, чтобы заставить правило выполниться, мы должны набрать 'make clean'. Смотрите главу 9 [Как запускать make].