next up previous contents
Next: Переменные в make-файлах Up: Средство управления проектом make Previous: Файл описания проекта -   Contents

Алгоритм работы make

Типичный make-файл проекта содержит несколько правил. Каждое из них имеет некоторую цель и некоторые зависимости. Смыслом работы make является достижение цели, которую она выбрала в качестве главной (default goal). Если главная цель является именем действия (т.е. абстрактной), то смысл работы make заключается в выполнении соответствующего действия. Если же главная цель является именем файла, то программа make должна построить самую "свежую" версию указанного файла.

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

make edit

Если не указывать какой-либо цели в командной строке, то make выбирает в качестве главной первую, встреченную в make-файле цель. Схематично ``верхний уровень'' алгоритма работы make можно представить так: make() { главная_цель = Выбрать_Главную_Цель() Достичь_Цели( главная_цель ) }

После того как главная цель выбрана, make запускает ``стандартную'' процедуру достижения цели. Сначала в make-файле ищется правило, которое описывает способ достижения этой цели (функция НайтиПравило). Затем, к найденному правилу применяется обычный алгоритм обработки правил (функция ОбработатьПравило): Достичь_Цели( Цель ) { правило = Найти_Правило( Цель ) Обработать_Правило( правило ) }

Обработка правила разделяется на два основных этапа. На первом этапе обрабатываются все зависимости, перечисленные в правиле (функция ОбработатьЗависимости). На втором этапе принимается решение - нужно ли выполнять указанные в правиле команды (функция НужноВыполнятьКоманды). При необходимости, перечисленные в правиле команды выполняются (функция ВыполнитьКоманды): Обработать_Правило( Правило ) { Обработать_Зависимости( Правило ) если Нужно_Выполнять_Команды( Правило ) { Выполнить_Команды( Правило ) } }

Функция ОбработатьЗависимости поочередно проверяет все перечисленные в теле правила зависимости. Некоторые из них могут оказаться целями каких-нибудь правил. Для этих зависимостей выполняется обычная процедура достижения цели (функция ДостичьЦели). Те зависимости, которые не являются целями, считаются именами файлов. Для таких файлов проверяется факт их наличия. При их отсутствии make аварийно завершает работу с сообщением об ошибке: Обработать_Зависимости( Правило ) { цикл от i=1 до Правило.число_зависимостей { если Есть_Такая_Цель( Правило.зависимость[i]) { Достичь_Цели( Правило.зависимость[i]) } иначе { Проверить_Наличие_Файла(Правило.зависимость[i]) } } }

На стадии обработки команд решается вопрос, нужно ли выполнять описанные в правиле команды или нет. Считается, что нужно выполнять команды если:

В противном случае (если ни одно из вышеприведенных условий не выполняется) описанные в правиле команды не выполняются. Алгоритм принятия решения о выполнении команд схематично можно представить так: Нужно_Выполнять_Команды( Правило ) { если Правило.Цель.Является_Абстрактной() return true // цель является именем файла если Файл_Не_Существует( Правило.Цель ) return true цикл от i=1 до Правило.Число_зависимостей { если Правило.Зависимость[i].Является_Абстрактной() return true иначе // зависимость является именем файла { если Время_Модификации(Правило.Зависимость[i]) > Время_Модификации( Правило.Цель ) return true } } return false }

В указанном примере целью по умолчанию является edit. Первым шагом по его обновлению будет обновление всех файлов объектов (.o), перечисленных как зависимости. Обновление edit.o в свою очередь требует обновления edit.cc и defs.h. Предполагается, что edit.cc является исходным файлом, из которого создается edit.o, а defs.h является заголовочным файлом, который включается в edit.cc. Правил, указывающих на эти файлы, нет; поэтому такие файлы, как минимум должны просто существовать. Теперь edit.o считается готовым, если он изменен позже, чем edit.cc или defs.h (если он старше их, это значит, что один из этих файлов изменился со времени последней компиляции edit.o). Если edit.o старше своих зависимостей, gmake выполняет действие OP ``gcc -g -c -Wall
edit.cc'', создавая новый edit.o. Когда edit.o и все другие файлы .o будут обновлены, они будут собраны вместе действием
``gcc -g -o edit ...'', чтобы создать программу edit, если либо edit еще не существует, либо любой из файлов .o новее, чем существующий файл edit.

Чтобы вызвать gmake для этого примера, используйте команду:

gmake -f <makefile-name> <target-names>,

где <target-names> - это имена целей, которые вы хотите обновить, а <makefile-name>, заданное после ключа -f, является именем make-файла. По умолчанию целью является первое правило в файле. Вы можете (обычно так и делают) опустить -f makefile-name, и в этом случае по умолчанию будет выбрано имя makefile или Makefile, если любой из этих файлов существует.

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

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

edit.o : edit.cc
gcc -g -c -Wall edit.cc
kbd.o : kbd.cc
gcc -g -c -Wall kbd.cc
commands.o : command.cc
gcc -g -c -Wall commands.cc
display.o : display.cc
gcc -g -c -Wall display.cc
insert.o : insert.cc
gcc -g -c -Wall insert.cc
search.o : search.cc
gcc -g -c -Wall search.cc
files.o : files.cc
gcc -g -c -Wall files.cc
utils.o : utils.cc
gcc -g -c -Wall utils.cc

edit.o kbd.o commands.o display.o $\backslash$
insert.o search.o files.o utils.o: defs.h
kbd.o commands.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

Порядок, в котором записаны эти правила, не имеет значения. Скорее, он зависит от вашего вкуса и выбора порядка группировки файлов.



2004-06-22