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

Утилита make

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

Утилита make

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

Make — это консольная утилита для автоматизации процессов сборки программного обеспечения в операционных системах семейства Unix и Linux. Инструмент анализирует зависимости между файлами проекта и выполняет необходимые действия только при наличии изменений.

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

  • Автоматическое определение зависимостей между файлами
  • Инкрементальная сборка (перекомпиляция изменённых компонентов)
  • Выполнение команд на основе правил конфигурации
  • Поддержка кроссплатформенной разработки

Инструмент стал стандартом индустрии в области построения систем от исходного кода до готовых исполняемых модулей. GNU version of make распространена в дистрибутивах Linux, macOS и BSD.


Структура 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)

Цели, которые не создают реальных файлов. Используются для вспомогательных операций очистки, настройки или запуска тестов.

.PHONY: clean install check

Определение .PHONY предотвращает ошибку, если в проекте есть файл с именем цели.

Псевдоцели системы (System 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)

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

Конфигурация для сборки библиотеки с подключением нескольких ресурсов.

LIBRARY = libproject.a

objects: math.o string.o network.o
ar rcs $(LIBRARY) objects

math.o: math.h
gcc -c math.c

string.o: string.h
gcc -c string.c

network.o: network.h
gcc -c network.c

При изменении math.h утилита пересоберёт только 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. Другие системы часто генерируют Makefiles для совместимости.


Руководство по разработке собственного 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


Освоение главы0%