Урок 11. Makefile: финальный проект — сборка конфига из частей

Финальный урок — большой проект, в котором встретится всё пройденное: цели-файлы, зависимости, переменные, параметры и цвета. Соберём конфиг приложения, который склеивается из двух частей: общей базы и «оверлея» окружения — dev для разработки, prod для «боя». Так устроены конфиги настоящих систем, и make для этого подходит идеально.

Шаг 1: исходные файлы

Создайте в папке урока три файла. Общая база — config/base.conf:

# базовые настройки (общие для всех окружений)
app_name = my-app
port = 8080
log_level = info

Оверлей разработки — config/dev.conf:

# оверлей окружения dev
debug = true
log_level = debug

И «боевой» оверлей — config/prod.conf:

# оверлей окружения prod
debug = false
log_level = error

Идея: оверлей приклеивается после базы, и его ключи перебивают базовые — в ini-подобных конфигах при повторе ключа действует последнее значение.

Шаг 2: шапка Makefile

Сначала всё знакомое: параметры с умолчаниями из урока про параметры, пути и цвета из урока про export:

APP_ENV ?= dev
CONFIG_DIR = config
OUT = out/app.conf

export cyan = `tput setaf 6`
export green = `tput setaf 2`
export reset = `tput sgr0`

Шаг 3: .PHONY

В этом проекте будут и цели-файлы, и цели-команды. Чтобы make никогда не путал вторые с файлами (грабли из урока про цель-файл), перечислим команды в директиве .PHONY:

.PHONY: help build show rebuild clean

Шаг 4: правило сборки

Сердце проекта — правило «файл из файлов»:

$(OUT): $(CONFIG_DIR)/base.conf $(CONFIG_DIR)/$(APP_ENV).conf
	@mkdir -p out
	@cat $^ > $@
	@echo "Собрано: ${green}$@${reset} (APP_ENV=$(APP_ENV))"

Новинка — $^: все исходники сразу, в порядке перечисления. Именно поэтому cat $^ клеит базу первой, а оверлей (имя которого зависит от $(APP_ENV)) — следом.

Шаг 5: цели-команды и справка

Вокруг правила — обвязка из привычных целей:

build: $(OUT)

show: build
	@cat $(OUT)

rebuild: clean build

clean:
	@rm -f $(OUT)
	@echo "Удалён файл $(OUT)"

Обратите внимание на пустую цель build: у неё нет своего рецепта — это просто «обёртка», заставляющая собраться файл out/app.conf.

И справка с цветами — как в уроке про цель по умолчанию, но многострочная: обратный слэш в конце строки продолжает команду, а \n даёт перенос в выводе:

help:
	@echo "${cyan}Сборка конфига${reset}, доступные команды: \
	     \n ${green}build${reset} - собрать конфиг (APP_ENV=prod) \
	     \n ${green}show${reset} - показать собранный конфиг \
	     \n ${green}rebuild${reset} - пересобрать с нуля \
	     \n ${green}clean${reset} - удалить собранное \
	"

Прогон

make build
Собрано: out/app.conf (APP_ENV=dev)

make build
make: Nothing to be done for 'build'.

make show
# базовые настройки (общие для всех окружений)
app_name = my-app
port = 8080
log_level = info
# оверлей окружения dev
debug = true
log_level = debug

Второй make build ничего не собирает — отслеживание из урока про цель-файл в действии. В собранном конфиге log_level встречается дважды: базовое info перебито оверлейным debug. Теперь «боевая» сборка — тем же механизмом пересборки из урока про зависимости:

APP_ENV=prod make rebuild
Удалён файл out/app.conf
Собрано: out/app.conf (APP_ENV=prod)

Задание

  1. Добавьте оверлей staging.conf (предрелизное окружение: debug = false, свой log_level) и соберите конфиг для него.
  2. Запустите make build APP_ENV=test — файла test.conf нет. Посмотрите, как make объясняет отсутствие исходника.
  3. Сделайте help первой целью файла — чтобы make без аргументов сразу показывал справку.

Это был последний урок курса: теперь любой Makefile — включая те, что встречаются в рабочих проектах с docker и деплоем, — читается как набор знакомых приёмов. Загляните в список уроков, если хотите что-то повторить, а полный код финального проекта лежит в репозитории: lesson11-project.