make
Запуск команд по правилам из Makefile — цели, зависимости, переменные, пересборка только устаревшего
make читает Makefile и выполняет правила: как получить цель из её зависимостей. Цель пересобирается, только если её нет или какая-то зависимость новее её (сравнивается время изменения файлов). Поэтому make подходит и для сборки, и как короткое меню команд проекта.
Синтаксис
make [опции] [цель…] [ПЕРЕМЕННАЯ=значение…]
Без цели выполняется первая цель в файле (или та, что задана в .DEFAULT_GOAL). Файл ищется под именами GNUmakefile, makefile, Makefile.
Правило в Makefile:
цель: зависимость1 зависимость2
команда # строка рецепта начинается с символа TAB
ещё_команда
Опции
| Опция | Что делает |
|---|---|
-n | показать команды, ничего не выполняя (dry run) |
-f файл | взять другой файл вместо Makefile |
-C каталог | перейти в каталог перед запуском |
-s | не печатать команды |
-B | пересобрать всё, не глядя на время файлов |
-j 4 | выполнять до 4 рецептов параллельно |
-k | при ошибке продолжать с другими целями, которые от неё не зависят |
-i | игнорировать ошибки во всех рецептах |
-q | ничего не делать, только код возврата: 0 — цель актуальна, 1 — нет |
-e | переменные окружения важнее присваиваний в Makefile |
-p | напечатать все правила и переменные (включая встроенные) |
--debug=b | объяснить, почему цель пересобирается |
Правила и цели
| Элемент | Что значит |
|---|---|
цель: зависимости | заголовок правила; строки с TAB под ним — рецепт |
a b: зависимости | одно правило для нескольких целей |
%.html: %.md | шаблонное правило: любой .html из одноимённого .md |
цель: обычные | порядковые | зависимости после | должны существовать, но их время не проверяется (удобно для каталогов) |
.PHONY: имя… | цели, которые не являются файлами (clean, test, build) |
.DEFAULT_GOAL := имя | цель по умолчанию вместо первой в файле |
.ONESHELL: | выполнять весь рецепт одной оболочкой, а не построчно |
include файл.mk | вставить другой Makefile |
Префиксы строк рецепта
| Префикс | Что делает |
|---|---|
@команда | не печатать саму команду перед выполнением |
-команда | продолжить, даже если команда завершилась с ошибкой |
\ в конце строки | продолжить команду на следующей строке (одна оболочка) |
Автоматические переменные
| Переменная | Значение |
|---|---|
$@ | имя текущей цели |
$< | первая зависимость |
$^ | все зависимости через пробел, без повторов |
$+ | все зависимости с повторами, в исходном порядке |
$? | зависимости, которые новее цели |
$* | «основа» имени в шаблонном правиле (что совпало с %) |
$(@D) / $(@F) | каталог / имя файла цели |
$$ | знак $ для оболочки ($$HOME, $$f в цикле) |
Переменные
| Запись | Что делает |
|---|---|
VAR = значение | значение раскрывается при каждом использовании |
VAR := значение | вычисляется сразу, в момент присваивания |
VAR ?= значение | задать, только если переменная ещё не задана (например, из окружения) |
VAR += ещё | дописать к значению через пробел |
$(VAR) или ${VAR} | подставить значение |
export VAR | передать переменную в окружение команд рецепта |
make VAR=x | значение из командной строки сильнее любого присваивания в файле |
Переменные окружения тоже видны как $(HOME), но присваивание в Makefile их перекрывает.
Функции
| Функция | Что возвращает |
|---|---|
$(wildcard src/*.c) | файлы по шаблону |
$(patsubst %.c,%.o,$(SRC)) | замена по шаблону в каждом слове |
$(SRC:.c=.o) | короткая запись той же замены суффикса |
$(subst from,to,текст) | простая замена подстроки |
$(notdir путь) / $(dir путь) | имя файла / каталог |
$(basename файл) / $(suffix файл) | без расширения / только расширение |
$(addprefix p/,a b) / $(addsuffix .txt,a b) | приписать префикс / суффикс к каждому слову |
$(filter %.c,список) / $(filter-out …) | оставить / убрать подходящие слова |
$(foreach x,список,текст) | повторить текст для каждого элемента |
$(shell команда) | вывод команды оболочки |
$(if усл,то,иначе) | условие внутри выражения |
$(error текст) / $(warning текст) | остановить с ошибкой / вывести предупреждение |
Условия
ifeq ($(MODE),prod)
FLAGS = -O2
else
FLAGS = -g
endif
ifdef DEBUG
FLAGS += -DDEBUG
endif
Также есть ifneq и ifndef. Условия проверяются при чтении файла, до выполнения рецептов.
Примеры
Небольшой Makefile, который склеивает два файла:
OUT = combined.txt
.PHONY: build clean
build: $(OUT)
$(OUT): part1.txt part2.txt
cat $^ > $@
clean:
rm -f $(OUT)
# что будет выполнено, без запуска
make -n
# собрать цель по умолчанию (первую — build)
make
# повторный запуск ничего не делает: цель новее зависимостей
make
# после изменения зависимости цель пересоберётся
touch part2.txt && make
# конкретная цель и переопределение переменной
make clean
make OUT=all.txt
# запустить Makefile из другого каталога
make -C /path/to/project build
# актуальна ли цель — для скриптов
make -q build && echo 'всё собрано'
Шаблонное правило, порядковая зависимость и цикл в рецепте:
SRC := $(wildcard src/*.md)
OUT := $(patsubst src/%.md,out/%.html,$(SRC))
.PHONY: all list
all: $(OUT)
out/%.html: src/%.md | out
cp $< $@
out:
mkdir -p $@
list:
@for f in $(SRC); do \
echo "source: $$f"; \
done
Подводные камни
missing separator. Stop.— строка рецепта начинается с пробелов, а не с TAB. Проверь черезcat -A Makefile: TAB виден как^I.- Каждая строка рецепта выполняется в отдельной оболочке.
cd dirна одной строке не влияет на следующую. Пишиcd dir && командав одну строку, продолжай строку через\или включи.ONESHELL:. - Цель без
.PHONYсовпала с файлом или каталогом. Если рядом есть файлtestили каталогdocs,make testответит'test' is up to dateи ничего не сделает. $в рецепте принадлежитmake. Переменная оболочки пишется$$VAR, а$VARmakeпоймёт как$(V)AR— получится пустота и хвостAR.- Ошибка команды останавливает
makeс ненулевым кодом, следующие строки рецепта не выполняются. Префикс-гасит ошибку, но злоупотреблять им не стоит. make -nпечатает и команды с@— так видно, что спрятано.=и:=ведут себя по-разному.A = $(B)возьмёт значениеBна момент использования,A := $(B)— на момент присваивания.$(shell …)с=будет запускаться при каждом обращении.- Пересборка опирается на время изменения. Если файл скопировали с сохранением старой даты,
makeможет решить, что цель актуальна. Тогда поможетmake -B. - Без
-jрецепты идут по очереди; с-jпорядок не гарантирован, поэтому все зависимости должны быть указаны явно.
См. также
git · variables · exit-codes · redirection