Что такое Makefile

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

Makefile – это текстовый файл, который можно редактировать в vim. Он определяет порядок выполнения действий: что, как и в какой последовательности нужно делать, чтобы добиться желаемого результата. Как правило, Makefile определяет последовательность компиляции программы, но в общем случае действия могут быть любыми командами операционной системы.

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

Структура Makefile

Основная часть Makefile состоит из описаний целей. Описание цели имеет вид:

имя цели: список зависимостей
  команды, которые нужно выполнить
Например, если текст программы, решающей задачу 21, хранится в файлах main.c, task_21.c и task_21.h, то правило для цели prog (имя выполняемого файла) может иметь следующий вид.
prog: main.c task_21.h task_21.c
    gcc -o prog main.c task_21.c 
Это правило означает следующее. Файл с именем prog считается устаревшим, если дата его создания меньше, чем дата создания одного из трех файлов, перечисленных в первой строчке после символа двоеточия (список зависимостей). Если prog устарел, то его можно обновить, выполнив приведенную во второй строчке команду gcc. Программа make сравнит даты создания файлов и, если хотя бы один файл из файлов-зависимостей изменился после создания prog, выполнит компиляцию.
Название цели должно начинаться в первом столбце. Перед текстом команды должен стоять символ табуляции. В приведенном примере перед командой gcc стоит символ табуляции. Если для обновления цели требуется несколько команд, то они должны быть одна под другой, по одной команде на строке с табуляцией в каждой строке.

Зависимости могут образовывать цепочки. Например, выполняемый файл с именем prog может зависеть от объектного файла main.o, который в свою очередь зависит от main.c. Если изменяется файл main.c, при выполнении команды make сначала будут выполнены команды, создающие файл main.o, а потом – команды, создающие prog. То есть Makefile определяет ацикличный ориентированный граф зависимостей. Порядок следования целей в файле значения не имеет: список зависимостей может ссылаться на цель, которая будет объявлена позднее.

В одном Makefile обычно определяется несколько целей. При запуске команды make можно указать имя цели, которую необходимо "сделать" (make). Например, make main.o. Если имя цели явно не задано при выполнении команды make, то программа make обрабатывает первую цель в файле. Первой целью Makefile принято делать цель с именем all, зависимости которой включают все программы. В этом случае можно выполнять команду make all (сделать все), которая выполнит все действия.

Переменные и шаблоны действий

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

Переменная определяется строчкой вида имя переменной=значение. Например,

CCFLAGS=-g -I./headers/
определяет переменную CCFLAGS (имя является общепринятым для обозначения списка флагов компилятора, C Compiler Flags) и присваивает ей значение "-g -I./headers/". Объявленные переменные можно использовать при описании целей и соответствующих им командах. Выражение $имя заменяется на значение переменной с именем имя. Например:
main.o: main.c
    gcc -c -o main.o $CCFLAGS main.c
Если в Makefile указано несколько подобных правил, то в случае необходимости изменить список флагов компилятора достаточно исправить только одну строчку – объявление переменной.

Еще более мощное средство – это шаблоны правил компиляции или неявные правила. Не вдаваясь в детали определения шаблонов рассмотрим следующий пример.

%.o: %.c
      gcc -c $CCFLAGS $< -o $@

main.o: main.c header.h
Первые две строчки определяют общее правило создания файлов с расширением .o из файлов с расширением .c. Команда, которая должна быть выполнена для создания .o-файла, содержит две специальные переменные. При выполнении этой команды вместо $< будет подставлено имя .c-файла, а вместо $@ – имя соответствующего .o-файла. Такие правила срабатывают в том случае, когда: Общие правила позволяют явно не писать команды компиляции для каждого исходного файла. Это удобно, так как обычно все комады компиляции одинаковые.

Третья строчка в приведенном примере устанавливает конкретную зависимость объектного файла main.o от файла main.c и других файлов. Команда компиляции берется из шаблона и для этого правила не указывается.

Цели без файлов

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

Для исключения описанной ситуации можно пометить некоторые цели специальным маркером:

.PHONY: all test
В данном случае при выполнении команд make all и make test действия, связанные с этими целями будут выполнены независимо от наличия или отсутствия файлов all и test в текущей директории.

Пример Makefile

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

# Общая часть
.PHONY: all debug
all: my_prog

# Создадим правило, которое используется для компиляции C-файлов 
%.o: %.c
      gcc -c -g $< -o $@

# Специальная часть, которая определяет конкретные имена файлов
integrate.o: integrate.c integrate.h
test.o: test.c integrate.h

my_prog: integrate.o test.o
      gcc -o my_prog integrate.o test.o

Для удобства запуска gdb в Makefile можно вставить цель debug

debug: my_prog
      gdb -x gdb.commands my_prog
которая запускает отладчик gdb и выполняет команды отладчика, записанные в файле gdb.commands (этот файл нужно создать). Возможное содержимое этого файла такое:
# Возможное содержимое файла gdb.commands
run           # Запускаем программу
bt            # Печатаем данные о текущем положении (в какой строке ошибка)
quit          # Выходим 
Конечно, это не является обязательным, но как было сказано выше, Makefile является средством явного описания знаний о компиляции программы. Если команда gdb не будет записана в файле, то со временем вы можете забыть, что нужно запустить для отладки.