Краткое руководство по Makefile и GNU Make для начинающих
GNU Make — это утилита для разработки, которая определяет, какие части конкретной базы кода необходимо перекомпилировать, и может выполнять команды Linux для выполнения этих операций.
Этот инструмент автоматизации сборки может использоваться с любым языком программирования, компиляцию которого можно выполнить из оболочки с помощью команд, что делает его незаменимым для C, C++ и многих других компилируемых языков.
Makefile’ы в Linux
Чтобы использовать GNU Make, нам нужен набор правил, определяющих взаимосвязь между различными файлами нашей программы, и команды для обновления каждого файла. Всё это записывается в специальный файл, называемый «Makefile» или «makefile».
Команда «make» использует базу данных makefile и время последней модификации файлов, чтобы определить, какие файлы необходимо перекомпилировать.
Содержимое Makefile
В общем, makefile содержит пять видов элементов: явные правила, неявные правила, определения переменных, директивы и комментарии.
- Явные правила определяют, как создавать или пересоздавать один или несколько файлов (называемых
targets) и когда это делать. - Неявные правила определяют, как создавать или восстанавливать файлы на основе их имен, описывая, как целевой файл связан с другим файлом с похожим именем.
- Определения переменных — это строки, которые задают строковые значения для переменных, чтобы их можно было подставлять позже в make-файле.
- Директивы — это инструкции для make, которые указывают, что нужно сделать особого при чтении makefile.
- Комментарии начинаются с символа
'#'. Любая строка, начинающаяся с'#', игнорируется make.
Структура Makefile
Информация о том, как make перекомпилировать систему, получается из чтения makefile.
Простой makefile состоит из правил со следующей синтаксисом:
target ... : prerequisites ... recipe ... ...
- Цель — это выходной файл, создаваемый программой, который также может быть фиктивной целью (объясняется ниже). Примеры включают исполняемые файлы, объектные файлы или фиктивные цели, такие как
clean,install,testилиall. - Предварительное условие (также называемое зависимостью) — это файл, используемый в качестве входных данных для создания целевых файлов.
- «Рецепт» — это действие, которое процесс 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 начинается с первой цели в makefile, называемой целью по умолчанию. В нашем примере первой целью является 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
Чтобы избежать такой дубликации, мы можем ввести переменные для хранения списков файлов. Использование переменных также облегчает сопровождение makefile.
Вот улучшенная версия:
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 — как нам различить файл и цель? Здесь и вступают в силу фантомные (phony) цели.
«Мнимая цель» на самом деле не является именем файла; это просто имя для рецепта, который выполняется при явном запросе. Основные причины использования «мнимых целей» — избежать конфликтов с файлами с таким же именем и улучшить производительность.
Вот важная деталь: рецепт для clean по умолчанию не выполняется при запуске make. Вместо этого вы должны явно вызвать его с помощью make clean.
Чтобы правильно объявить фиктивную цель, используйте директиву .PHONY:
.PHONY: clean all install clean: rm -f $(OBJ) $(TARGET) all: $(TARGET)
Современные лучшие практики Makefile
Вот несколько современных практик, которые стоит рассмотреть:
Используйте правила шаблона, чтобы уменьшить повторение:
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@
Добавьте цель по умолчанию «все»:
.PHONY: all all: $(TARGET)
Включите генерацию зависимостей для автоматического отслеживания заголовков:
DEPS = $(OBJ:.o=.d) -include $(DEPS)
Добавьте больше фиктивных целей для обычных операций:
.PHONY: install test run
Используйте автоматические переменные, такие как $@ (цель), $> (первое требование) и $^ (все требования), чтобы сделать правила более универсальными.
Заключение
Теперь попробуйте создать makefile для вашего собственного кода. GNU Make по-прежнему является мощным и широко используемым инструментом сборки, особенно для проектов на C и C++.
Понимание makefile поможет вам работать с многочисленными проектами с открытым исходным кодом и даст вам детальный контроль над процессом сборки. Не стесняйтесь оставлять комментарии с вашими вопросами или опытом!

Добавить комментарий