Перейти к основному содержимому

Утилита make

Разработчику Архитектору Инженеру

Утилита make

Место в разделе "Терминал"

make не заменяет оболочку (Bash/PowerShell) — вы по-прежнему вводите команды в терминале, а Makefile описывает какие цели собрать и в каком порядке, если изменились исходники.

Типичный вызов — make, make clean, make test.

Команды внутри правил — те же, что в скриптах Unix; часто сборку запускают по SSH — PuTTY; проверка API после деплоя — curl, curl / fetch — примеры.


Общая характеристика утилиты

Вы изменили один файл utils.cpp в проекте из десяти модулей. Пересобирать всё вручную (g++ для каждого .cpp, потом линковка) долго и легко ошибиться в порядке. Make читает файл Makefile, сравнивает время изменения исходников и объектов и запускает только нужные команды — в примере перекомпилирует utils.o и заново слинкует программу.

Make — консольная утилита для автоматизации сборки в Unix/Linux/macOS (и в WSL на Windows). Это не компилятор — make вызывает gcc, g++, go build, npm run — что прописано в правилах.

Ключевые характеристики:

  • Автоматическое определение зависимостей между файлами (по правилам в Makefile).
  • Инкрементальная сборка — пересборка только устаревших частей.
  • Выполнение команд оболочки из правил (как в Bash).
  • Одинаковый интерфейс make target для разных языков и проектов.

GNU Make (gmake) — де-факто стандарт в Linux и macOS. CMake и Meson часто генерируют Makefile под make или Ninja — см. проект и фреймворки.


Минимальный пример с нуля

Создайте каталог и два файла:

hello.c

#include <stdio.h>
int main(void) {
printf("Hello, make\n");
return 0;
}

Makefile (отступ перед командой — символ Tab, не пробелы):

hello: hello.c
gcc -Wall -o hello hello.c

.PHONY: clean
clean:
rm -f hello

В терминале (Linux):

make # собрать цель hello (первая цель — по умолчанию)
./hello
make clean # удалить бинарник

Повторный make без изменений выведет что-то вроде make: 'hello' is up to date — лишняя компиляция не запускается. После правки hello.c make снова вызовет gcc.


Структура Makefile

Makefile представляет собой текстовый файл с правилами построения выходных объектов из входных данных. Файл должен располагаться в корне проекта или быть указан явным образом при запуске утилиты.


Основные элементы конфигурационного файла

ЭлементНазначениеПример
Цель (Target)Имя создаваемого объекта или результата сборкиapp
Зависимость (Dependency)Исходный файл или объект, требуемый для создания целиmain.c
Команда (Recipe)Действие по созданию целевого файлаgcc -c main.c
ПеременнаяХранит значение для повторного использования в правилахCC = gcc
КомментарииСтроки, начинающиеся символом ## Настройка компилятора

Синтаксические правила

  1. Отступ в командах. Каждая команда в правиле должна начинаться с символа табуляции (Tab). Использование пробелов приводит к ошибке выполнения.
  2. Разделители. Точка с запятой разделяет несколько команд в одной строке. Перенос строки завершает действие.
  3. Расширения. Make определяет расширения файлов автоматически через внутренние переменные.
  4. Специальные цели. Особые метки типа .PHONY указывают на цели, которые не являются реальными файлами.

Алгоритм работы Make

Процесс выполнения включает последовательность шагов для определения необходимости выполнения действий.


Этапы обработки

  1. Поиск конфигурационного файла. Утилита ищет файлы Makefile, makefile, GNUmakefile в текущем каталоге.
  2. Загрузка правил. Parse-процесс читает все определения целей и зависимостей.
  3. Выбор целевой задачи. Система выбирает цель all по умолчанию или ту, что указана в параметрах запуска.
  4. Анализ времен меток. Утилита сравнивает даты изменения зависимостей с датой создания цели.
  5. Выполнение команд. Make запускает команды для обновления устаревших файлов.

Логика инкрементальной сборки

Если файл цели существует
Если дата файла цели > даты всех зависимостей
Пропустить выполнение команд для этой цели
Иначе
Выполнить команду обновления
Иначе
Создать цель через команду

Этот подход минимизирует время простоя разработчика. При изменении одного исходного файла пересобирается только цепь его зависимостей. Полный пересбор проекта происходит только при отсутствии оптимизации.


Типы целей в Makefile

Существует несколько категорий целей для организации структуры сборки.


Обычные цели (File Targets)

Реальные файлы, создаваемые процессом сборки. Make отслеживает их существование и статус времени записи.

program: program.o helper.o
$(CC) -o program program.o helper.o

В данном примере program — это обычный файл, который создаётся компиляцией объектных модулей.


Физически не сущие цели (Phony Targets)

Цели, которые не создают файлов с тем же именем на диске. Типичные имена — clean, test, install, all. Make всё равно сравнивает даты, и если в каталоге случайно лежит файл clean, без .PHONY утилита может решить, что цель "уже собрана" и не запустит команды очистки.

.PHONY: clean install check all

Объявление .PHONY говорит: "эти имена — команды, а не артефакты сборки". Всегда помечайте clean, test, help как phony.


Псевдоцели системы (Система Prerequisites)

Внутренние правила, управляющие поведением системы сборки.

ПсевдоцельОписание
.SUFFIXESОпределение суффиксов для сопоставления типов файлов
.DEFAULTПравило, используемое при отсутствии подходящего определения
.INTERMEDIATEОбъекты, удаляемые после завершения сборки
.PRECIOUSФайлы, которые не удаляются даже при прерывании процесса

Специальные предустановленные переменные

Переменные задают поведение инструмента без участия пользователя.

ПеременнаяЗначение по умолчанию
MAKEFLAGSПараметры запуска самого Make
SHELLИнтерпретатор команд (обычно /bin/sh)
RMКоманда удаления файлов (rm -f)
VPATHПуть поиска зависимостей в директориях

Структура правила сборки

Каждое правило состоит из трёх обязательных частей для корректной работы утилиты.


Базовая схема правила

ЦЕЛЬ : ЗАВИСИМОСТИ
КОМАНДА_1
КОМАНДА_2

Требования к оформлению:

  • Название цели заканчивается двоеточием
  • Зависимости перечисляются через пробелы
  • Отступ в строках с командами — один символ Tab
  • Нет пустых строк между целью/зависимостью и командами

Пример правильного оформления

build: source.txt template.txt
cat source.txt >> result.txt
echo "---" >> result.txt

Что должно соблюдаться:

  1. build — название цели в отдельной строке
  2. source.txt template.txt — список зависимостей
  3. Табуляция перед командами сборки

Примеры применения

Проект C/C++ с двумя модулями

Сценарий типичного консольного приложения с разделением на исходные и объектные файлы.

# Глобальные переменные проекта
CC = gcc
CFLAGS = -Wall -g
LDFLAGS = -lm

# Исходный код программы
SRC_FILES = main.cpp utils.cpp config.cpp
OBJ_FILES = $(SRC_FILES:.cpp=.o)

# Основная цель сборки
APP_NAME = myapp
$(APP_NAME): $(OBJ_FILES)
$(CC) $(LDFLAGS) -o $(APP_NAME) $(OBJ_FILES)

# Паттерн создания объектных файлов
%.o: %.cpp
$(CC) $(CFLAGS) -c $< -o $@

# Цель очистки от мусора сборки
.PHONY: clean
clean:
rm -f $(OBJ_FILES) $(APP_NAME)

# Установка программы
.PHONY: install
install: $(APP_NAME)
cp $(APP_NAME) /usr/local/bin/

# Запуск тестов
.PHONY: test
test: $(APP_NAME)
./$(APP_NAME) --run-tests

Комментарии к схеме:

  • $(SRC_FILES) преобразует список .cpp в соответствующий набор .o
  • %.o: %.cpp — правило со спецификацией шаблонов для всех файлов
  • $< содержит первую зависимость (main.cpp)
  • $@ содержит имя цели (main.o)

Проект на Node.js (npm)

Сборка фронтенда или утилиты на Node часто сводится к npm ci и npm run build. Makefile фиксирует порядок и даёт те же цели, что в CI:

.PHONY: all install clean test lint

NODE ?= node
NPM ?= npm

all: build

node_modules: package.json package-lock.json
$(NPM) ci
@touch node_modules

build: node_modules
$(NPM) run build

test: node_modules
$(NPM) test

lint: node_modules
$(NPM) run lint

install: build
$(NPM) run start --if-present

clean:
rm -rf node_modules dist build .next
  • Цель node_modules пересобирается, когда меняется package-lock.json (инкрементальность make).
  • make test в CI/CD вызывают так же, как локально — меньше расхождений "у меня работает".
  • Крупные монорепозитории часто переходят на Turborepo/Nx; для учебного или среднего проекта Makefile остаётся наглядным.

Проект на Go

Go приносит встроенную сборку (go build), но Makefile по-прежнему удобен для обёртки, версии и нескольких бинарников:

.PHONY: all build clean test

BINARY = bin/app
MAIN = ./cmd/app
GO ?= go
LDFLAGS = -s -w

all: build

build: $(BINARY)

$(BINARY): $(shell find . -name '*.go' -not -path './vendor/*')
@mkdir -p bin
$(GO) build -ldflags "$(LDFLAGS)" -o $(BINARY) $(MAIN)

test:
$(GO) test ./...

clean:
rm -rf bin/

go build сам кэширует компиляцию; make здесь добавляет единые имена целей (make, make test, make clean) для команды и сервера, куда вы заходите по SSH.


Множественные входные файлы

Конфигурация для статической библиотеки .a из нескольких модулей:

CC = gcc
AR = ar
LIBRARY = libproject.a
OBJS = math.o string.o net.o

$(LIBRARY): $(OBJS)
$(AR) rcs $@ $(OBJS)

math.o: math.c math.h
$(CC) -c math.c -o $@

string.o: string.c string.h
$(CC) -c string.c -o $@

net.o: net.c net.h
$(CC) -c net.c -o $@

$@ — имя цели (math.o или libproject.a), $< — первая зависимость (math.c). При изменении только math.h make пересоберёт math.o и заново соберёт архив, потому что у libproject.a изменилась зависимость math.o.


Работа с вложенными директориями

Пример сборки проектов с распределённой структурой.

SUBDIRS = src lib tests
BUILD_DIRS = $(addsuffix /build,$(SUBDIRS))

all: $(BUILD_DIRS)
$(MAKE) -C src
$(MAKE) -C lib
$(MAKE) -C tests

%:
mkdir -p $@

.PHONY: all

Команда -C переключает контекст в указанную директорию перед запуском make.


Управление параметрами сборки

Пользователь может передавать параметры во время выполнения утилиты.


Передача контекстных переменных

make CC=clang CFLAGS="-O2" APP_NAME=debug-app

Эта команда изменит компилятор на clang и установит уровень оптимизации -O2 без изменения Makefile.


Выбор частей сборки

make build
make clean
make test
make install

Утилита выполнит только указанную цель. Цель all активируется без параметров.


Расширенные возможности

Условия и вычисляемые выражения

ifeq ($(OS),Linux)
LINKER_FLAGS = -lpthread
endif

ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG -g
endif

Конструкция ifeq проверяет совпадение условий. Значения переменных можно проверять динамически.


Генерация зависимостей автономно

-include $(OBJECTS:.o=.d)

Директива -include подключает дополнительные файлы зависимостей. Они создаются компилятором отдельно.


Использование Wildcards и Globbing

SOURCE_FILES := $(wildcard *.c)
HEADER_FILES := $(wildcard *.h)

ALL_SOURCES := $(SOURCE_FILES) $(HEADER_FILES)

Функция wildcard подбирает все файлы согласно маске в директории.


Типичные проблемы и решения

ПроблемаПричинаРешение
Ошибка missing separatorИспользован пробел вместо табуляцииЗаменить пробелы на символ Tab
Повторное создание существующего файлаОтсутствие правильной проверки меток времениДобавить проверку через timestamp()
Неправильная подстановка переменныхОтсутствие скобок при вызове функцииИспользовать $( и ) вокруг имени
Удаление промежуточных файловОтказ от .INTERMEDIATEИзменить настройку удаления

Сравнение с другими системами сборки

КритерийMakeCMakeNinja
Язык правилMakefile.cmakeninja.build
Порог входаНизкийСреднийСредний
Скорость работыСредняяВысокаяОчень высокая
Поддерживаемые платформыВсе UnixВсе ОСВсе ОС
Размер проектаМалый, среднийЛюбойЛюбой
Наличие IDE интеграцииЧастичнаяПолнаяПолная

Make остаётся базовой утилитой для многих библиотек open-source: достаточно make && sudo make install. CMake описывает проект один раз и генерирует Makefile или Ninja; в CI/CD часто цепочка cmake .. && cmake --build . вместо ручного Makefile.


Полезные флаги при отладке

make -n # показать команды, не выполняя (dry run)
make -j4 # до 4 параллельных рецептов (ускорение на многоядерном CPU)
make -B # принудительно пересобрать все цели
make -f Other.mk # другой файл правил

Если make пишет missing separator — почти всегда в рецепте пробелы вместо Tab; редактор с отображением пробелов/табов помогает сразу.


Руководство по разработке собственного Makefile

Шаг 1. Определение структуры проекта

Разделите проект на логические блоки. Исходный код отделите от бинарников и документов.

SRCDIR = src
BUILDDIR = build
BINDIR = bin
DOCDIR = docs

Шаг 2. Определение целей сборки

Создайте основные точки входа для процесса.

.PHONY: all debug release clean package

Шаг 3. Добавление переменных сбора

Задайте пути, флаги и инструменты в начале файла.

CC = clang
AR = ar
RANLIB = ranlib
INCLUDES = -Iinclude

Шаг 4. Создание реализации правил

Опишите каждый этап сборки с помощью команд.

all: $(BINDIR)/app

$(BINDIR)/app: $(BUILDDIR)/lib.a
$(CC) -o $@ $^

$(BUILDDIR)/lib.a: $(SRCDIR)/module.o
$(AR) rcs $@ $^

Шаг 5. Тестирование правил

Проверьте выполнение каждого этапа вручную перед финальным релизом.


Рекомендации по организации работы

Перед началом разработки:

  1. Создайте файл Makefile в корне проекта
  2. Определите глобальные переменные среды
  3. Зафиксируйте имена целей для сборки
  4. Добавьте цели clean и check

Во время разработки:

  1. Проверяйте работу make на тестовом компьютере
  2. Используйте make -n для демонстрации команд без исполнения
  3. Проверяйте вывод через make VERBOSE=1
  4. Сохраняйте логи при отладке ошибок

После финализации:

  1. Документируйте структуру сборки в README
  2. Укажите требования к версии инструмента
  3. Предоставьте примеры установки и запуска

Контрольный список проверки Makefile

Перед выпуском проекта убедитесь в наличии следующих элементов.

[ ] Цель all определена явно
[ ] Цели .PHONY указаны для несуществующих файлов
[ ] Отступы выполнены символом Tab
[ ] Команды используют правильные кавычки
[ ] Переменные экранированы правильно
[ ] Цель clean удаляет все созданные файлы
[ ] Компилятор настраивается через переменную
[ ] Зависимости описаны полностью
[ ] Файл открывается в любой системе Unix


Смежные материалы

Раздел "Терминал"оглавление, скрипты Unix, команды Linux, итоги раздела.

Разработкавыполнение кода, проект и фреймворки, C++ — о разделе.

Инфраструктураосновы DevOps; сборка на удалённом сервере — PuTTY и SSH; проверка API и health-check — утилита curl, curl / fetch — примеры.

Windows — make обычно через WSL или MSYS2; нативная автоматизация — PowerShell.


Содержание