Skip to content

GNU Make

Dmitry Ponyatov edited this page Aug 8, 2019 · 9 revisions

GNU Make

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

К счастью, для задачи автоматического запуска компиляторов и утилит при изменении файлов проектов уже более 50 лет существует стандартная утилита make, которая поставляется в комплекте с любым компилятором, или (в случае Linux) устанавливается отдельным пакетов дистрибутива. Создав в каталоге проекта обычный текстовый файл Makefile, вы можете всего в десяток строчек прописать все зависимости между файлами, и как вызывать каждую утилиту или компилятор при их изменении.

Если запустить

$ make

то программа попытается найти файл с именем по умолчание Makefile в текущем каталоге и выполнить инструкции из него. Если в текущем каталоге есть несколько мейкфайлов, то можно указать на нужный вот таким образом:

make -f MyMakefile

секции Makefile

Код для make имеет два типа:

  1. определение переменных через =
  2. правила

Правило имеет вид

цель : [источник ...]
<tab> команда
...
  • цель -- файл(ы) который должен быть обновлен при изменении даты редактирования любого из
  • файлов-источников с помощью
  • команд отделенных слева символов табуляции (НЕ пробелами -- настройте ваш редактор, чтобы не заменял)

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

Другое применение -- в обратную сторону: в источники прописываются файлы, необходимые для сборки проекта (компиляции программы), и если таких файлов не существует, они будут созданы соответствующими правилами.

переменные

Переменные позволяют назначить символьным именам значения, которые могут поставляться:

A = B присвоение значения, подставляемого вместо имени переменной $(A)
C = обнуление переменной, вместо имени переменной будет подставляться пустая строка
D ?= E условное назначение, если D ранее не была определена

Переменные могут быть заданы не только в Makefile, но и в командной строке при вызове make. Так вы можете передавать настройки сборки проекта, например можно явно указать, для какой платы вы собираете свою игру:

~/itstep$ make HW=rpi3 mygame

переменные подстановки цели/источников

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

$@ цель
$< первый источник
$^ все источники
$? измененные источники

стандартные переменные

Некоторым переменным приписано определенное значение

CC команда запуска компилятора Си
CXX команда запуска компилятора С++
LD линкер
CFLAGS
CXXFLAGS
LDFLAGS
MAKE

типовые переменные в проектах

каталоги

MODULE = $(notdir $(CURDIR))

CWD = $(CURDIR)
GZ  = $(CWD)/gz
TMP = $(CWD)/tmp
SRC = $(TMP)/src
MODULE модуль -- имя текущего каталога\проекта
CWD корневой каталог проекта из которого вызывается make
GZ каталог для архивов исходного кода
TMP временный каталог, используемый при сборке из исходников
SRC сюда распаковываются исходники программных пакетов

разбор примера Makefile используемого в Buildroot

## Makefile
# комментарий

.PHONY: os dirs gz

## по умолчанию при запуске `make` срабатывает самое первое правило
## для явного запуска используйте команду `make os`

os: dirs gz

## прописываем набор переменных для пакетов

# версия

BR_VER = 2019.02.4

# полное название пакета с версией

BR     = buildroot-$(BR_VER)

# имя файла с архивом исходного кода

BR_GZ  = $(BR).tar.gz

## набор типовых переменных предпочтительнее определять в самом начале `Makefile`

MODULE = $(notdir $(CURDIR))

CWD = $(CURDIR)
GZ  = $(CWD)/gz
TMP = $(CWD)/tmp
SRC = $(TMP)/src

# готовим набор каталогов

dirs:
	mkdir -p $(GZ) $(TMP) $(SRC)

# загрузка архивов, необходимых для сборки системы
	
gz: $(GZ)/$(BR_GZ)

# при отсутствии архива создаем его загрузкой с сети
# версия подставляется через переменную

$(GZ)/$(BR_GZ):
	wget -c -O $@ https://github.com/buildroot/buildroot/archive/$(BR_VER).tar.gz
Clone this wiki locally