Make
Make — утилита в области информационных технологий, предназначенная для разработки программного обеспечения. Автоматически собирает файлы, часто исполняемые файлы или библиотеки из исходных компонентов, таких как исходный код. Для этого используются специальные файлы — makefile, в которых указывается, как собирать целевые файлы. В отличие от простого shell-скрипта, выполняет команды только при необходимости. Цель — получить результат (скомпилированное или установленное программное обеспечение, созданная документация и т. д.), не выполняя все этапы заново. Особенно широко используется на платформах UNIX.
Общие сведения
| Make | |||
|---|---|---|---|
| Тип | утилита | ||
| Автор | Стюарт Фельдман | ||
| Написана на | Си | ||
| Операционная система | UNIX | ||
| Первый выпуск | 1977 | ||
| |||
История
В 1970-х годах компиляция программ становилась всё более длительной и сложной, требуя множества взаимосвязанных этапов. Большинство тогдашних систем основывались на shell-скриптах, что вынуждало повторять все этапы даже при незначительных изменениях. В этом контексте Make был разработан доктором Стюартом Фельдманом в 1977 году, когда он работал в Лабораториях Bell. Управляя зависимостями между исходными и скомпилированными файлами, Make позволяет перекомпилировать только те части, которые изменились после модификации исходного файла.
С момента оригинальной разработки появилось множество вариантов программы. Наиболее известны версии для BSD и проекта GNU — последняя используется по умолчанию в системах Linux. Эти варианты добавляют новые функции и, как правило, несовместимы между собой. Например, скрипты, написанные для GNU Make, могут не работать в BSD Make.
Позднее появились инструменты для автоматической генерации конфигурационных файлов (Makefile), используемых Make. Такие инструменты анализируют зависимости (automake) или конфигурацию системы (autoconf) для создания сложных Makefile, адаптированных к конкретной среде компиляции.
Make вдохновил появление различных систем управления сборкой, как специализированных для отдельных платформ (rake, ant), так и универсальных, например, Ninja в 2010-х годах.
В 2003 году доктор Фельдман был удостоен премии ACM за разработку Make[1].
Принцип работы
Процесс компиляции разбивается на элементарные правила вида:
Цель A зависит от B и C. Чтобы создать A, нужно выполнить определённую последовательность команд
Все эти правила помещаются в файл, обычно называемый Makefile. Команды представляют собой элементарные действия компиляции, редактирования связей, генерации кода.
Зависимости соответствуют файлам — это могут быть исходные файлы или промежуточные результаты сборки. Цель обычно представляет собой файл, но может быть и абстрактной.
Сборка A выполняется командой:
make A
Make проверяет, существуют ли файлы B и C и актуальны ли они, то есть не были ли их зависимости изменены после их создания. В противном случае Make сначала рекурсивно собирает B и C. Затем Make выполняет команды для создания A, если файл с таким именем отсутствует или если файл A старше B или C.
Обычно используется цель all, охватывающая весь проект; эта абстрактная цель зависит от всех файлов, которые нужно собрать. Другие часто используемые цели: install — для выполнения команд установки проекта, clean — для удаления всех сгенерированных файлов.
Make поддерживает явные правила зависимостей (по именам файлов) и неявные (по шаблонам файлов); например, любой файл с расширением .o может быть собран из файла с тем же именем и расширением .c с помощью команды компиляции.
Make представляет проект в виде дерева зависимостей, а некоторые варианты программы позволяют собирать несколько целей параллельно, если между ними нет зависимостей.
Makefile
Make ищет makefile в текущем каталоге. Например, GNU Make ищет по порядку файлы GNUmakefile, makefile, Makefile, затем выполняет указанные (или стандартные) цели только для этого файла.
Язык makefile относится к декларативному программированию. В отличие от императивного, здесь не важен порядок выполнения инструкций.
Makefile состоит из правил. Самая простая форма правила:
цель [цель ...]: [компонент ...]
[табуляция] команда 1
.
.
.
[табуляция] команда n
«Цель» чаще всего — файл, который нужно собрать, но может определять и действие (удалить, скомпилировать и т. д.). «Компоненты» — это необходимые для выполнения действия зависимости. Иными словами, «компоненты» — это цели других правил, которые должны быть выполнены до выполнения данного правила. Правило определяет действие как последовательность «команд». Каждая команда должна начинаться с символа табуляции.
Команды выполняются отдельным shell или интерпретатором командной строки.
Пример makefile:
all: цель1 цель2
echo ''all : ok''
цель1:
echo ''цель1 : ok''
цель2:
echo ''цель2 : ok''
При запуске этого makefile командой make all или make будет выполнено правило 'all'. Оно требует выполнения компонентов 'цель1' и 'цель2', для которых определены отдельные правила. Эти правила будут выполнены автоматически до правила 'all'. Команда make цель1 выполнит только правило 'цель1'.
Для определения порядка выполнения правил make использует топологическую сортировку.
Правило не обязательно должно содержать команду.
Компоненты не всегда являются целями других правил, они могут быть и файлами, необходимыми для сборки целевого файла:
выход.txt: файл1.txt файл2.txt
cat файл1.txt файл2.txt > выход.txt
Это правило создаёт файл выход.txt из файлов файл1.txt и файл2.txt. При выполнении makefile Make проверяет, существует ли выход.txt, и если нет — создаёт его с помощью указанной команды.
Строки команд могут иметь один или несколько из трёх префиксов:
- Минус (-) — ошибки игнорируются;
- @ — команда не выводится в стандартный поток вывода перед выполнением;
- Плюс (+) — команда выполняется даже если make запущен в режиме «не выполнять».
Если несколько целей требуют одних и тех же компонентов и собираются одними и теми же командами, можно определить множественное правило. Например:
all: цель1 цель2
цель1: текст1.txt
echo текст1.txt
цель2: текст1.txt
echo текст1.txt
можно заменить на:
all: цель1 цель2
цель1 цель2: текст1.txt
echo текст1.txt
Обратите внимание: цель all обязательна, иначе будет выполнена только цель цель1.
Для определения текущей цели можно использовать переменную $@. Например:
all: цель1.txt цель2.txt
цель1.txt цель2.txt: текст1.txt
cat текст1.txt > $@
В результате будут созданы два файла — цель1.txt и цель2.txt — с содержимым текст1.txt.
Makefile может содержать макроопределения. Макросы традиционно записываются заглавными буквами:
МАКРО = определение
Макросы чаще всего используются как переменные, если содержат простые строки, например CC = gcc. Переменные окружения также доступны как макросы. Макросы в makefile могут быть переопределены аргументами командной строки. Команда будет следующей:
make МАКРО="значение" [МАКРО="значение" ...] ЦЕЛЬ [ЦЕЛЬ ...]
Макросы могут содержать shell-команды с использованием обратных кавычек (`):
DATE = ` date `
Существуют также «внутренние» макросы make:
- $@ — имя цели;
- $? — имена всех компонентов, более новых, чем цель;
- $< — первый компонент правила;
- $^ — все компоненты правила.
Макросы позволяют пользователям указывать, какие программы использовать или какие параметры применять при сборке. Например, макрос CC часто используется для указания компилятора C.
Для использования макроса его нужно подставить, заключив в $(). Например, чтобы использовать макрос CC, пишут $(CC). Также допустимо использовать ${}. Например:
НОВЫЙ_МАКРО = $(МАКРО)-${МАКРО2}
Эта строка создаёт макрос НОВЫЙ_МАКРО из содержимого МАКРО и МАКРО2.
Существует несколько способов определить макрос:
- = — присваивание по ссылке (так называемое «рекурсивное раскрытие»);
- := — присваивание по значению («простое раскрытие»);
- ?= — условное присваивание, только если макрос ещё не определён;
- += — присваивание с конкатенацией, предполагает, что макрос уже существует.
Эти правила позволяют создавать makefile для определённого типа файлов. Существуют два типа суффиксных правил: двойные и простые.
Двойное суффиксное правило определяется двумя суффиксами: «суффикс цели» и «суффикс источника». Такое правило распознаёт любой файл типа «цели». Например, если суффикс цели — '.txt', а суффикс источника — '.html', правило эквивалентно '%.txt : %.html' (где % — любое имя файла). Простое суффиксное правило требует только суффикса источника.
Синтаксис для двойного суффиксного правила:
.suffix_source.suffix_target :
Важно: суффиксное правило не может иметь дополнительных компонентов.
Синтаксис для задания списка суффиксов:
.SUFFIXES: .suffix_source .suffix_target
Пример использования суффиксных правил:
.SUFFIXES: .txt .html
# преобразует .html в .txt
.html.txt:
lynx -dump $< > $@
Следующая команда преобразует файл файл.html в файл.txt:
make -n файл.txt
Использование суффиксных правил считается устаревшим из-за их ограниченности.
Пример Makefile:
# Указать, какой компилятор использовать.
CC ?= gcc
# Задать параметры компилятора.
CFLAGS ?= -g
LDFLAGS ?= -L/usr/openwin/lib
LDLIBS ?= -lX11 -lXext
# Распознать расширения *.c и *.o как суффиксы.
SUFFIXES ?= .c .o
.SUFFIXES: $(SUFFIXES) .
# Имя исполняемого файла.
PROG = life
# Список объектных файлов для итоговой программы.
OBJS = main.o window.o Board.o Life.o BoundedBoard.o
all: $(PROG)
# Этап компиляции и компоновки:
# ВАЖНО: строки с "$(CC)" начинаются с символа ТАБУЛЯЦИИ, а не пробелами.
$(PROG): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $(PROG) $(OBJS)
.c.o:
$(CC) $(CFLAGS) -c $*.c
В этом примере .c.o — неявное правило. По умолчанию цели — это файлы, но если указаны два суффикса подряд, правило применяется к любому файлу с соответствующим расширением.
Для достижения этой цели выполняется команда $(CC) $(CFLAGS) -c $*.c, где $* — имя файла без суффикса.
Цель all зависит от $(PROG) (то есть от life, который является файлом).
$(PROG) (то есть life) зависит от $(OBJS) (то есть файлов main.o window.o Board.o Life.o и BoundedBoard.o). Для этого make выполняет команду $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $(PROG) $(OBJS).
Синтаксис CC ?= gcc (или в общем виде <переменная> ?= <значение>) присваивает <значение> переменной только если она ещё не определена. Если <переменная> уже содержит значение, эта инструкция не действует.
Ограничения
Ограничения Make напрямую связаны с простотой его концепций: файлы и даты. Эти критерии недостаточны для одновременного обеспечения эффективности и надёжности.
Критерий даты для файлов имеет два недостатка. Если файл не находится на неизменяемом носителе, нет гарантии, что дата файла соответствует дате последнего изменения.
Для непривилегированного пользователя можно быть уверенным, что данные не новее указанной даты, но точная дата их появления не гарантируется.
Таким образом, при любом изменении даты файла вся сборка может быть признана необходимой, если речь идёт об исходнике, или, что хуже, ненужной, если речь идёт о целевом файле.
- В первом случае теряется эффективность.
- Во втором — надёжность.
Хотя файлы и даты необходимы для любого движка сборки, ориентированного на надёжность и эффективность, их недостаточно, что иллюстрируют следующие примеры:
- Make полностью игнорирует семантику обрабатываемых файлов, не анализируя их содержимое. Например, не различается рабочий код и комментарии. Поэтому при добавлении или изменении комментария в файле на C Make сочтёт, что все зависящие от него библиотеки или программы нужно пересобрать.
- Если результат зависит от входных данных, он зависит и от применяемых действий, описанных в makefile. Редко makefile пишут так, чтобы при изменении самого makefile вся сборка признавалась устаревшей; для этого нужно явно добавить makefile в зависимости всех правил. Технически это возможно, но приведёт к пересборке всех целей, даже если изменения их не касаются.
- Аналогично, если переменные, влияющие на сборку, задаются через переменные окружения или командную строку, Make не может определить, влияют ли они на сборку, особенно если речь идёт о параметрах, а не о файлах.
Ещё одно ограничение Make — отсутствие автоматической генерации списка зависимостей и невозможность проверки его корректности. Например, правило .c.o ошибочно: объектные файлы зависят не только от соответствующего исходного .c, но и от всех файлов, включённых в этот .c. Более реалистичный список зависимостей:
$(PROG): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $(PROG) $(OBJS)
main.o: main.c Board.h BoundedBoard.h Life.h global.h
$(CC) $(CFLAGS) -c main.c
window.o: window.c window.h global.h
$(CC) $(CFLAGS) -c window.c
Board.o: Board.c Board.h window.h global.h
$(CC) $(CFLAGS) -c Board.c
Life.o: Life.c Life.h global.h
$(CC) $(CFLAGS) -c Life.c
BoundedBoard.o: BoundedBoard.c BoundedBoard.h Board.h global.h
$(CC) $(CFLAGS) -c BoundedBoard.c
Системные файлы (например, stdio.h) обычно не включают в зависимости, так как они редко меняются. Некоторые версии Make позволяют обойти эту проблему с помощью анализаторов, автоматически формирующих списки зависимостей.
По этим причинам современные системы сборки либо специализируются на определённых языках (учитывая семантику файлов), либо используют базы данных для хранения всей информации о сборке (аудит сборки, трассируемость).
Альтернативы
Существуют различные альтернативы Make:
- makepp — производная от (GNU) Make, дополнительно включает расширяемый анализатор команд и включаемых файлов для автоматического определения зависимостей. Изменения параметров командной строки и другие влияния также учитываются. Проблема рекурсивного Make легко решается, обеспечивая корректную сборку[2]
- clearmake — версия, интегрированная в ClearCase. Тесно связана с системой управления версиями, ведёт аудит всех сборок. Это позволяет игнорировать проблемы дат, переменных окружения, скрытых зависимостей и параметров командной строки (см. ограничения).
- ant — преимущественно для Java.
- rake — аналог для Ruby.
- SCons — полностью отличается от Make, включает некоторые функции инструментов сборки типа autoconf. Для расширения можно использовать Python.
- Speedy Make использует XML для makefile, очень прост в написании, предлагает больше автоматизации, чем Make.
- mk — аналог Make, изначально созданный для системы Plan 9; проще Make, а способ взаимодействия с командным интерпретатором делает его чище и столь же мощным, но он несовместим с Make.
Примечания
- ↑ Matthew Doar. Practical Development Environments : [англ.]. — O'Reilly Media, 2005. — P. 94. — ISBN 978-0-596-00796-6.
- ↑ Makepp Home PageEsperantoEnglish
Ссылки
- Сайт реализации GNU
- Раздельная компиляция и make (примеры на C)
- Памятка — Makefile