Урок 10. Makefile: цель-файл — make сам отслеживает изменения
Primary tabs
Помните из урока про историю, зачем Фелдман придумывал make? Пересобирать только то, что устарело. До этого урока наши цели были просто «именами команд» — make выполнял их без вопросов. Настало время увидеть ту самую главную идею: цель может быть файлом.
Исходник и результат
Создадим исходный конфиг config/base.conf:
# базовый конфиг app_name = my-app port = 8080
И опишем правило «файл из файла»:
out/app.conf: config/base.conf @mkdir -p out @echo "Пересобираю: $< -> $@" @cat $< > $@
Читается так: цель — файл out/app.conf, его исходник — config/base.conf. Зависимости мы знаем по уроку про цепочки целей — только теперь зависимость не «другая цель», а файл.
Разбор рецепта: $@ и $
В рецепте появились две автоматические переменные — make заполняет их сам, для каждого правила:
$@— имя цели (у насout/app.conf);$<— первая зависимость, то есть первое имя в списке после двоеточия. В нашем правиле зависимость — файл-исходникconfig/base.conf, поэтому$<подставляет его.
Подстановка работает точно так же, как с обычными переменными из урока про переменные: перед запуском make заменяет их текстом. Наш рецепт превращается в:
@echo "Пересобираю: config/base.conf -> out/app.conf" @cat config/base.conf > out/app.conf
Первый шаг — сообщение для человека (с уже знакомым «тихим» @ из урока про тихий вывод), второй — собственно сборка: содержимое исходника уходит в файл цели.
Смотрим на магию
Первый запуск всё собирает:
make out/app.conf Пересобираю: config/base.conf -> out/app.conf
А второй — не делает ничего:
make out/app.conf make: 'out/app.conf' is up to date.
«Up to date» — «актуально»: make сравнил время изменения файлов, увидел, что результат свежее исходника, и не стал работать. Изменим исходник — цель это заметит и пересоберётся:
touch config/base.conf make out/app.conf Пересобираю: config/base.conf -> out/app.conf
(команда touch обновляет время изменения файла, не трогая содержимое — удобно для экспериментов)
Когда make выполняет рецепт
Правило такое:
- файл с именем цели существует — make сравнивает время его изменения с исходниками: рецепт выполнится, только если цель устарела;
- файла нет — цель считается «всегда устаревшей», и рецепт выполняется при каждом запуске.
Именно поэтому наши прежние цели-команды — hello, help, clean — честно срабатывали каждый раз: файлов с такими именами в папке нет.
Отсюда же известные грабли: если рядом с Makefile окажется файл, случайно совпавший именем с целью-командой (скажем, файл clean), make сочтёт цель «актуальной» и откажется выполнять рецепт. Лечится это директивой .PHONY — явным обещанием «эта цель никогда не файл»; она ждёт нас в финальном уроке.
Задание
- Соберите цепочку из трёх файлов:
a.conf←b.conf←c.conf(каждый собирается из следующегоcat-ом). Поменяйте самый первый исходник и посмотрите, какие цели пересоберутся. - Добавьте в зависимость цели второй файл и проверьте, что попадёт в
$<— подсказка: только первый исходник. Как взять «все исходники сразу», узнаем в следующем уроке. - Создайте файл, совпадающий именем с одной из ваших целей-команд (
touch clean), и попробуйте её запустить — снимите «грабли» и объясните себе, что произошло.
Следующий урок — финальный проект: соберём конфиг приложения из базовой части и оверлея окружения dev/prod, соединив всё пройденное. Полный план — в списке уроков курса.
Пример этого урока лежит в репозитории: lesson10-file-target.
- Log in to post comments
- 9 reads