Структура make-файлов

Утилита make, автоматически определяет какие части большой программы должны быть перекомпилированы, и выполняет необходимые для этого действия.

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

После того, как нужный make-файл создан, простой команды :

make

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

При вызове make, в командной строке могут быть заданы параметры, указывающие, какие файлы следует перекомпилировать и каким образом это делать.

Простой make-файл

Простой make-файл состоит из "правил" (rules) следующего вида:


цель ... : пререквизит ...
        команда
        ...
        ...

Обычно, цель (target) представляет собой имя файла, который генерируется в процессе работы утилиты make. Примером могут служить объектные и исполняемый файлы собираемой программы. Цель также может быть именем некоторого действия, которое нужно выполнить (например, clean - очистить).

Пререквизит (prerequisite) - это файл, который используется как исходдные данные для порождения цели. Очень часто цель зависит сразу от нескольких файлов.

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

Обычно, команды находятся в правилах с пререквизитами и служат для создания файла-цели, если какой-нибудь из пререквизитов был модефицирован. Однако, правило, имеющее команды, не обязательно должно иметь пререквизиты. Например, правило с целью clean ("очистка"), содержащее команды удаления, может не иметь пререквизитов.

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

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

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

В данном примере, заголовочный файл 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 clean

В приведенном примере, целями, в частности, являются объектные файлы main.o и kbd.o, а также исполняемый файл edit. К пререквизитам относятся такие файлы, как main.c и defs.h. Каждый объектный файл, фактически, является одновременно и целью и пререквизитом. Примерами команд могут служить cc -c main.c и cc -c kbd.c.

В случае, если цель является файлом, этот файл должен быть перекомпилирован или перекомпонован всякий раз, когда был изменен какой-либо из его пререквизитов. Кроме того, любые пререквизиты, которые сами генерируются автоматически, должны быть обновлены первыми. В нашем примере, исполняемый файл edit зависит от восьми объектных файлов; объектный файл main.o зависит от исходного файла main.c и заголовочного файла defs.h.

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

Цель clean является не файлом, а именем действия. Поскольку, при обычной сборке программы это действие не требуется, цель clean не является пререквизитом какого-либо из правил. Следовательно, make не будет "трогать" это правило, пока вы специально об этом не попросите. Заметьте, что это правило не только не является пререквизитом, но и само не содержит каких-либо пререквизитов. Таким образом, единственное предназначение данного правила - выполнение указанных в нем команд. Цели, которые являются не файлами, а именами действий называются абстрактными целями (phony targets). Абстрактные цели подробно рассматриваются в разделе Абстрактные цели. В разделе Ошибки при выполнении команд описано, как заставить make игнорировать ошибки, которые могут возникнуть при выполнении команды rm и любых других команд.

Использование переменных

В приведенном выше примере, в правиле для 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-файла, используются переменные. Переменные (variables) позволяют, один раз определив текстовую строку, затем использовать ее многократно в нужных местах).

Обычной практикой при построении make-файлов является использование переменной с именем objects, OBJECTS, objs, OBJS, obj, или OBJ, которая содержит список всех объектных файлов программы. Например:


objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
   

Далее, всякий раз, когда будет необходим список объектных файлов, можно использовать значение этой переменной с помощью записи $(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 : 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-файл включаются также и другие цели помимо компиляции:


 1: # Makefile
 2:
 3: 0BJS = foo.о bar.о baz.o
 4: LDLIBS = -L/usr/local/lib/ -lbar
 5:
 6: foo: $(OBJS)
 7: 	gcc -o foo $ (OBJS) $ (LDLIBS)
 8:
 9: install: foo
 10: 	install -m 644 foo /usr/bin
 11: .PHONY: install
 
  • Строка 1 — это комментарий; make следует обычной традиции Unix определения комментариев с помощью символа #.
  • В строке 3 определяется переменная по имени 0BJS как foo.о bar.o baz.о.
  • В строке 4 определяется другая переменная — LDLIBS.
  • В строке 6 начинается определение правила, которое указывает на то, что файл foo зависит от (в этом случае, собран из) файлов, имена которых содержатся в переменной OBJS. foo называется целевым объектом, а $ (OBJS) — списком зависимостей. Обратите внимание на синтаксис расширения переменной: имя переменной помещается в $(...).
  • Строка 7 — это командная строка, указывающая на то, как построить целевой объект из списка зависимостей. Командных строк может быть много, и первым символом в командной строке должна быть табуляция.
  • Строка 9 — довольно интересный целевой объект. Фактически тут не предпринимается попытка создать файл по имени install; вместо этого foo инсталлируется в /usr/bin с помощью стандартной программы install. Эта строка вызывает неоднозначность в make: что, если файл install уже существует и является более новым, нежели foo? В этом случае запуск команды make install приведет к выдаче сообщения 'install' is up to date (install новее) и завершению работы.
  • Строка 11 указывает make на то, что install не является файлом, и что необходимо игнорировать любой файл по имени install при вычислении зависимости install. Таким образом, если зависимость install была вызвана (как это сделать мы рассмотрим далее), команда в строке 10 всегда будет выполняться.
    . PHONY — это директива, которая изменяет операцию make; в этом случае она указывает make на то, что целевой объект install не является именем файла.
    Целевые объекты . PHONY часто используются для совершения действий вроде инсталляции или создания одиночного имени целевого объекта, которое основывается на нескольких других уже существующих целевых объектов, например:
    all: foo barbaz
    .PHONY: all

    К сожалению, . PHONY не поддерживается некоторыми версиями make. Менее очевидный, не такой эффективный, но более переносимый способ для этого показан ниже.
    all: foo bar baz FORCE
    FORCE:

    Это срабатывает только тогда, когда нет файла по имени FORCE.

Суффиксные правила

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

 

.с.о:
$(СС) -с $(CFLAGS) $(CPPFLAGS) -о $@ $<
.SUFFIXES: .с .о

В этом правиле говорится (если не касаться излишних деталей), что make должна, если не было других явных указаний, превратить файл а.с в а.о путем запуска приложенной командной строки. Каждый файл будет рассматриваться так, будто он явно внесен в список в качестве зависимости соответствующего файла в вашем make-файле.
Это суффиксное правило демонстрирует другую возможность make — автоматические переменные. Понятно, что нужно найти способ подставить зависимость и целевой объект в командную строку. Автоматическая переменная $@ выступает в качестве целевого объекта, $< выступает в качестве первой зависимости, а $^ представляет все зависимости.
Существуют и другие автоматические переменные, которые рассматриваются в руководстве по make. Все автоматические переменные можно использовать в обыкновенных правилах, а также в суффиксных и шаблонных правилах. Последняя строка примера представляет еще одну директиву .SUFFIXES указывает make на то, что и являются суффиксами, которые должен использовать make для нахождения способа превратить существующие исходные файлы в необходимые целевые объекты.
Шаблонные правила более мощные и, следовательно, немного сложнее, чем суффиксные правила. Ниже приведен пример эквивалентного шаблонного правила для показанного выше суффиксного правила.

%.о: %.с
$(СС) -с $(CFLAGS) $(CPPFLAGS) -о $@ $<

Большинство крупных проектов с открытым исходным кодом используют инструменты Automake, Autoconf и Libtool. Эти инструменты представляют собой коллекцию знаний об особенностях различных систем и стандартах сообщества, которая может помочь в построении проектов. Таким образом, потребуется писать лишь немного кода, специфического для проекта. Например, Automake пишет целевые объекты install и uninstall, Autoconf автоматически определяет возможности системы и настраивает программное обеспечение для его соответствия системе, a Libtool отслеживает различия в управлении совместно используемыми библиотеками на разных системах.