Краткое введение в Makefile и GNU Make для начинающих

GNU Make — это утилита для разработки, которая определяет, какие части конкретной кодовой базы необходимо перекомпилировать, и может выполнять команды Linux для выполнения этих операций.
Этот инструмент для автоматизации сборки можно использовать с любым языком программирования, компиляция которого выполняется из командной строки с помощью команд, что делает его незаменимым для C, C++ и многих других компилируемых языков.
Создание файлов в Linux
Чтобы использовать GNU Make, нам нужен набор правил, определяющих взаимосвязь между различными файлами в нашей программе, а также команды для обновления каждого файла. Они записываются в специальный файл под названием «Makefile» или «makefile».
Команда make использует базу данных makefile и время последнего изменения файлов, чтобы определить, какие файлы необходимо перекомпилировать.
Содержимое файла Makefile
Как правило, make-файлы содержат пять типов элементов: явные правила, неявные правила, определения переменных, директивы и комментарии.
- Явные правила определяют, как создавать или обновлять один или несколько файлов (называемых
targets
) и когда это делать. - Неявные правила определяют, как создавать или пересоздавать файлы на основе их названий, и описывают, как целевой файл связан с другим файлом с похожим названием.
- Определения переменных — это строки, в которых указаны строковые значения переменных, которые будут подставлены позже в make-файле.
- Директивы — это инструкции для make, которые позволяют сделать что-то особенное при чтении make-файла.
- Комментарии начинаются с символа
'#'
. Любая строка, начинающаяся с'#'
, игнорируется make.
Структура Make -файлов
Информация, которая сообщает make, как перекомпилировать систему, поступает из файла makefile.
Простой makefile состоит из правил со следующим синтаксисом:
target ... : prerequisites ... recipe ... ...
- Target — это выходной файл, созданный программой, который также может быть фиктивной целью (см. ниже). В качестве примеров можно привести исполняемые файлы, объектные файлы или фиктивные цели, такие как
clean
,install
,test
, илиall
. - Prerequisites (также называемое зависимостью) — это файл, который используется в качестве входных данных для создания целевых файлов.
- Recipe — это действие, которое выполняет команда make для создания целевого файла на основе предварительных условий. Перед каждой строкой рецепта необходимо ставить символ табуляции, если только вы не укажете переменную
.RECIPEPREFIX
для определения другого символа в качестве префикса.
Пример файла Makefile:
final: main.o end.o inter.o start.o gcc -o final main.o end.o inter.o start.o main.o: main.c global.h gcc -c main.c end.o: end.c local.h global.h gcc -c end.c inter.o: inter.c global.h gcc -c inter.c start.o: start.c global.h gcc -c start.c clean: rm -f main.o end.o inter.o start.o final
В этом примере мы используем четыре исходных файла C и два заголовочных файла для создания исполняемого файла final. Каждый файл .o'
является одновременно целью и необходимым условием в makefile. Обратите внимание на последнюю цель с именем clean
— это действие, а не реальный файл.
Поскольку обычно нам не нужно выполнять очистку во время компиляции, она не указана в качестве обязательного условия ни в одном из других правил. Цели, которые не связаны с файлами, а представляют собой просто действия, называются фиктивными целями. Как правило, у них нет обязательных условий, как у обычных целевых файлов.
Как GNU Make обрабатывает Makefile
По умолчанию make начинает работу с первой цели в make-файле, которая называется целью по умолчанию. В нашем примере final
— это первая цель. Поскольку для её выполнения требуются объектные файлы, они должны быть обновлены перед созданием final
. Каждая предпосылка обрабатывается в соответствии с собственным правилом.
Перекомпиляция происходит, если были внесены изменения в исходные или заголовочные файлы или если объектный файл вообще не существует. После перекомпиляции необходимых объектных файлов make решает, нужно ли выполнять повторную компоновку final
. Это происходит, если файл final не существует или если какой-либо из объектных файлов новее него.
Например, если мы изменим inter.c
и запустим make, то исходный файл будет перекомпилирован для обновления inter.o
и последующей компоновки final
.
Использование переменных в Makefile-файлах
В нашем примере нам пришлось дважды перечислить все файлы объектов в правиле для final
:
final: main.o end.o inter.o start.o gcc -o final main.o end.o inter.o start.o
Чтобы избежать такого дублирования, мы можем использовать переменные для хранения списков файлов. Использование переменных также упрощает поддержку make-файла.
Вот улучшенная версия:
CC = gcc CFLAGS = -Wall -Wextra -O2 OBJ = main.o end.o inter.o start.o TARGET = final $(TARGET): $(OBJ) $(CC) -o $(TARGET) $(OBJ) main.o: main.c global.h $(CC) $(CFLAGS) -c main.c end.o: end.c local.h global.h $(CC) $(CFLAGS) -c end.c inter.o: inter.c global.h $(CC) $(CFLAGS) -c inter.c start.o: start.c global.h $(CC) $(CFLAGS) -c start.c clean: rm -f $(OBJ) $(TARGET)
Обратите внимание, что мы также определили CC для компилятора и CFLAGS для флагов компиляции, что позволяет легко менять параметры компилятора или даже переключаться между компиляторами для всего проекта.
Правила очистки исходного каталога
Как видно из примера, мы можем задать правила для очистки исходного каталога путем удаления ненужных файлов после компиляции. Но предположим, что у нас есть реальный файл с именем clean
— как отличить файл от цели? Здесь на помощь приходят фиктивные цели.
фиктивная цель на самом деле не является именем файла; это просто имя рецепта, который выполняется по явному запросу. Основные причины использования фиктивных целей — избежание конфликтов с файлами с таким же именем и повышение производительности.
Важная деталь: рецепт для clean
не будет выполняться по умолчанию при запуске make. Вместо этого его нужно явно вызвать с помощью make clean
.
Чтобы правильно объявить фиктивную цель, используйте директиву .PHONY
:
.PHONY: clean all install clean: rm -f $(OBJ) $(TARGET) all: $(TARGET)
Современные рекомендации по созданию файлов Makefile
Вот несколько современных практик, которые стоит рассмотреть:
Используйте шаблонные правила, чтобы сократить количество повторений:
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@
Добавьте цель по умолчанию all:
.PHONY: all all: $(TARGET)
Включите генерацию зависимостей для автоматического отслеживания заголовков:
DEPS = $(OBJ:.o=.d) -include $(DEPS)
Добавьте больше фиктивных целей для стандартных операций:
.PHONY: install test run
Используйте автоматические переменные, такие как $@
(цель), $>
(первое условие) и $^
(все условия), чтобы сделать правила более универсальными.
Заключение
Теперь попробуйте создать make-файлы для своей кодовой базы. GNU Make остаётся мощным и широко используемым инструментом сборки, особенно для проектов на C и C++.
Понимание makefiles поможет вам работать с бесчисленным множеством проектов с открытым исходным кодом и даст вам возможность детально контролировать процесс сборки. Не стесняйтесь задавать вопросы или делиться опытом в комментариях!
Редактор: AndreyEx