В разработке программного обеспечения Make — это инструмент автоматизации сборки , который создает исполняемые программы и библиотеки из исходного кода путем чтения файлов, называемых make-файлами , которые определяют, как получить целевую программу. Хотя для управления процессом сборки также можно использовать интегрированные среды разработки и функции компилятора , специфичные для языка , Make по-прежнему широко используется, особенно в Unix и Unix-подобных операционных системах .
Make не ограничивается созданием программ. Его также можно использовать для управления любым проектом, в котором некоторые файлы необходимо автоматически обновлять из других файлов при каждом изменении других файлов.
Make — одна из наиболее распространенных утилит сборки для отслеживания зависимостей, в первую очередь благодаря ее раннему включению в Unix , начиная с PWB/UNIX 1.0, в которой представлено множество инструментов для задач разработки программного обеспечения. [1] Он был создан Стюартом Фельдманом в апреле 1976 года в Bell Labs . [2] [3] [1] Фельдман получил в 2003 году премию ACM Software System Award за разработку этого инструмента. [4]
На написание Make Фельдмана вдохновил опыт коллеги, который безуспешно отлаживал свою программу, в которой исполняемый файл случайно не обновлялся с изменениями:
Make возник во время визита Стива Джонсона (автора yacc и т. д.), который ворвался в мой офис, проклиная Судьбу, заставившую его потратить утро на отладку правильной программы (ошибка была исправлена, файл не был скомпилирован,
cc *.o
поэтому не пострадал). Поскольку часть предыдущего вечера я провел, пытаясь справиться с той же катастрофой в проекте, над которым работал, возникла идея инструмента для ее решения. Все началось с тщательно продуманной идеи анализатора зависимостей, свелось к чему-то гораздо более простому и в те выходные превратилось в Make. Использование еще влажных инструментов было частью культуры. Makefiles были текстовыми файлами, а не магически закодированными двоичными файлами, потому что это был дух Unix : пригодные для печати, отлаживаемые и понятные вещи.- Стюарт Фельдман, Искусство программирования для Unix , Эрик С. Рэймонд, 2003 г.
До появления Make система сборки Unix чаще всего состояла из зависящих от операционной системы сценариев оболочки «make» и «install», сопровождающих исходный код их программы. Возможность объединить команды для целей сборки в один файл и возможность абстрагировать отслеживание зависимостей и обработку архивов стала важным шагом в направлении современных сред сборки.
Make переписывался много раз, включая новые реализации, которые используют тот же формат файлов и основные алгоритмические принципы, а также предоставляют нестандартные улучшения. Примеры:
POSIX включает стандартизацию основных функций и работы утилиты Make и реализован с различной степенью совместимости с версиями Make на базе Unix. В общем, простые make-файлы можно с достаточным успехом использовать между различными версиями Make. GNU Make, Makepp и некоторые версии BSD Make по умолчанию сначала ищут файлы с именами «GNUmakefile», [35] «Makeppfile» [36] и «BSDmakefile» [37] соответственно, что позволяет помещать файлы makefile, использующие определяемые реализацией файлы. поведение в отдельных локациях.
Make традиционно используется для компиляции файлов исходного кода (например *.c, *.ts
, файлов и т. д.) в целевой тип объектного файла , который может быть напрямую выполнен либо как отдельный исполняемый файл (как в случае с исходным кодом C ), либо нет (как в случае для исходного кода TypeScript ).
Make не ограничивается только объектными файлами: он применим в любом случае использования, когда набор файлов зависит от содержимого другого набора файлов. Make включает механизм зависимостей для правильного управления этими отношениями. Возможный вариант использования, не связанный с программированием, — обнаружить изменение, внесенное в файл изображения (источник), а затем преобразовать файл в определенный формат, скопировать результат в систему управления контентом и отправить электронное письмо набору пользователи, указывающие, что действия были выполнены.
Make вызывается со списком имен целей в виде аргументов командной строки :
сделать [ ЦЕЛЬ ... ]
Без аргументов Make создает первую цель, которая появляется в ее make-файле, который традиционно представляет собой символическую «фальшивую» цель с именем all , которая не соответствует ни одному сохраненному файлу.
Make решает, нужно ли восстанавливать цель, сравнивая время изменения файла. [38] Это решает проблему предотвращения создания файлов, которые уже обновлены, но это терпит неудачу, когда файл изменяется, но время его модификации остается в прошлом. Такие изменения могут быть вызваны восстановлением более старой версии исходного файла или тем, что сетевая файловая система является источником файлов, и ее часы или часовой пояс не синхронизированы с компьютером, на котором работает Make. Пользователь должен справиться с этой ситуацией, принудительно выполнив полную сборку. И наоборот, если время изменения исходного файла наступит в будущем, это приведет к ненужной перестройке, что может доставить неудобства пользователям.
Makefiles также традиционно используются для предоставления команд для автоматизации общих задач разработки программного обеспечения . Один из таких make-файлов вызывается из командной строки:
make # Без аргументов запускается первым TARGET
make help # Показать доступные ЦЕЛИ
make dist # Создать архив релизов из текущего каталога
make check # Модульное тестирование без установки
Make ищет файл makefile в текущем каталоге, например, GNU Make ищет файлы по порядку, чтобы найти файл с именем GNUmakefile , makefile или Makefile . а затем вызывает указанные цели (или цели по умолчанию) из этого файла.
Язык makefile похож на декларативное программирование . [40] [41] [42] [43] Этот класс языков, в котором описываются необходимые конечные условия, но порядок выполнения действий не важен, иногда сбивает с толку программистов, привыкших к императивному программированию .
Распространенной проблемой при автоматизации сборки является адаптация процесса сборки к конкретной платформе . Например, компилятор, используемый на одной платформе, может не принимать те же параметры, что и на другой. Эта проблема обычно решается путем создания инструкций сборки для конкретной платформы, которые, в свою очередь, обрабатываются Make. Обычными инструментами для этого процесса являются Autoconf , CMake или GYP (или более продвинутый NG ).
Makefiles могут содержать пять типов конструкций: [44]
#
, используются для комментариев .Makefile состоит из правил . Каждое правило начинается с текстовой строки зависимости , которая определяет цель, за которой следует двоеточие (:) и, при необходимости, перечисление компонентов (файлов или других целей), от которых зависит цель. Строка зависимости устроена так, что цель (левая часть двоеточия) зависит от компонентов (правая часть двоеточия). Обычно компоненты называют предварительными условиями цели. [45]
цель [цель ...]: [компонент ...] Tab ↹[команда 1] . . .Tab ↹[команда н]
Обычно каждое правило имеет одну уникальную цель, а не несколько целей.
Например, объектный файл C .o создается из файлов .c, поэтому файлы .c идут первыми (т. е. конкретная цель объектного файла зависит от исходного файла C и файлов заголовков ). Поскольку сам Make не понимает, не распознает и не различает различные типы файлов, это открывает возможность человеческой ошибки.
За каждой строкой зависимостей может следовать серия командных строк с отступом TAB, которые определяют, как преобразовать компоненты (обычно исходные файлы) в целевые (обычно «выходные данные»). Если какое-либо из предварительных условий имеет более позднее время изменения, чем целевое, запускаются командные строки. В документации GNU Make команды, связанные с правилом, называются «рецептом».
Первая команда может появиться в той же строке после предварительных условий, разделенных точкой с запятой.
цели : предпосылки ; команда
например,
привет : ; @ эхо " привет "
Make может решить, с чего начать, посредством топологической сортировки .
Каждая командная строка должна начинаться с символа табуляции, чтобы ее можно было распознать как команду. Табуляция представляет собой пробел , но символ пробела не имеет такого специального значения. Это проблематично, поскольку визуальной разницы между табуляцией и рядом пробельных символов может не быть. Этот аспект синтаксиса make-файлов часто подвергается критике; Эрик С. Рэймонд описал его как «один из худших провалов дизайна в истории Unix» [46], а в «Руководстве для ненавистников Unix» говорится, что «использование табуляции как части синтаксиса похоже на одну из тех ловушек из палочек для тарзанки в Зеленые береты ». Фельдман объясняет свой выбор решением проблемы ранней реализации, сохраняющейся стремлением к обратной совместимости с самыми первыми пользователями:
Почему вкладка в столбце 1? Якк был новым, Лекс был совершенно новым. Я тоже не пробовал, поэтому решил, что это будет хороший повод поучиться. После того, как я запутался во время своего первого удара по Лексу, я просто сделал что-то простое с шаблоном новой строки-вкладки. Это сработало, оно осталось. А через несколько недель у меня было около дюжины пользователей, большинство из которых были друзьями, и я не хотел испортить свою встроенную базу. Остальное, к сожалению, уже история.
- Стюарт Фельдман [46]
GNU Сделать. начиная с версии 3.82 позволяет выбирать любой символ (один символ) в качестве префикса рецепта с помощью специальной переменной .RECIPEPREFIX, например:
.RECIPEPREFIX := : all : :@echo "символ префикса рецепта установлен на '$(.RECIPEPREFIX)'"
Каждая команда выполняется отдельной оболочкой или экземпляром интерпретатора командной строки . Поскольку операционные системы используют разные интерпретаторы командной строки, это может привести к созданию непереносимых make-файлов. Например, GNU Make (все версии POSIX) по умолчанию выполняет команды с /bin/sh , тогда как обычно используются команды Unix , такие как cp . В отличие от этого, nmake от Microsoft выполняет команды с помощью cmd.exe, где доступны пакетные команды, такие как копирование , но не обязательно cp.
В правиле рецепт может быть опущен. Линия зависимости может состоять исключительно из компонентов, которые ссылаются на другие цели, например:
Realclean : очистить дистчистку
Командные строки правила обычно располагаются так, чтобы генерировать цель. Пример: если файл file.html более новый, он преобразуется в текст. Содержимое make-файла:
файл.txt : файл . html lynx -dump файл.html > файл.txt
Вышеупомянутое правило будет срабатывать при обновлении файла «file.txt». В следующем вызове Make обычно использует это правило для обновления целевого объекта «file.txt», если «file.html» более новый.
создать файл.txt
Командные строки могут иметь один или несколько из следующих трех префиксов:
Игнорирование ошибок и подавление эха также можно получить с помощью специальных целей .IGNORE
и .SILENT
. [47]
Microsoft NMAKE имеет предопределенные правила, которые можно исключить из этих make-файлов, например .c.obj $(CC)$(CFLAGS)
Makefile может содержать определения макросов. Макросы обычно называются переменными , если они содержат простые определения строк, например . Макросы в make-файлах могут быть переопределены в аргументах командной строки , передаваемых утилите Make. Переменные среды также доступны в виде макросов.CC=clang
Макросы позволяют пользователям указывать вызываемые программы и другое пользовательское поведение во время процесса сборки. Например, макрос CC
часто используется в make-файлах для ссылки на расположение компилятора C , и пользователь может захотеть указать конкретный компилятор для использования.
Новые макросы (или простые «переменные») традиционно обозначаются заглавными буквами:
МАКРО = определение
Макрос используется путем его расширения. Традиционно это делается путем включения его имени внутри файла $()
. (Отсутствие круглых скобок приводит к тому, что Make интерпретирует следующую букву после the $
как полное имя переменной.) В эквивалентной форме вместо круглых скобок используются фигурные скобки, т. е. ${}
такой стиль используется в BSD .
NEW_MACRO = $( МАКРОС ) - $( МАКРОС2 )
Макросы могут состоять из команд оболочки с помощью оператора подстановки команд , обозначаемого обратными кавычками ( `
).
ГГГГММДД = ` дата` _
Содержимое определения сохраняется «как есть». Используется отложенное вычисление , что означает, что макросы обычно расширяются только тогда, когда их раскрытие действительно необходимо, например, при использовании в командной строке правила. Расширенный пример:
ПАКЕТ = пакет ВЕРСИЯ = ` дата + "%Y.%m.%d" ` АРХИВ = $( ПАКЕТ ) - $( ВЕРСИЯ ) dist : # Обратите внимание, что только теперь макросы расширяются для интерпретации оболочкой: # tar -cf package-`date +"%Y.%m.%d"`.tartar -cf $( АРХИВ ) .tar .
Общий синтаксис для переопределения макросов в командной строке:
make МАКРОС = «значение» [ МАКРОС = «значение» ... ] ЦЕЛЬ [ ЦЕЛЬ ... ]
Makefiles могут получить доступ к любому из множества предопределенных внутренних макросов , наиболее распространенным из которых является ?
и .@
target : компонент 1 компонент 2 # содержит те компоненты, которые требуют внимания (т.е. они МОЛОЖЕ текущей ЦЕЛИ). эхо $? # оценивается как текущее имя ЦЕЛИ, находящееся слева от двоеточия. эхо $@
Несколько распространенным расширением синтаксиса является использование += , ?= и != вместо знака равенства. Он работает как на BSD, так и на GNU. [48]
Правила суффиксов имеют «цели» с именами в форме .FROM.TO
и используются для запуска действий на основе расширения файла. В командных строках правил суффиксов POSIX указывает [49] , что внутренний макрос $<
ссылается на первое предварительное условие и $@
ссылается на цель. В этом примере, который преобразует любой HTML-файл в текст, токен перенаправления оболочки >
является частью командной строки, а $<
макрос ссылается на HTML-файл:
.СУФФИКСЫ : . текст . HTML# Из .html в .txt .html.txt : lynx -dump $< > $@
При вызове из командной строки приведенный выше пример расширяется.
$ make -n file.txt lynx -dump file.html > file.txt
Правила суффиксов не могут иметь собственных предварительных условий. [50] Если они есть, они рассматриваются как обычные файлы с необычными именами, а не как правила суффиксов. GNU Make поддерживает правила суффиксов для совместимости со старыми make-файлами, но в остальном поощряет использование шаблонных правил . [51]
Правило шаблона выглядит как обычное правило, за исключением того, что его цель содержит ровно один %
символ в строке. Целью считается шаблон для сопоставления имен файлов: она %
может соответствовать любой подстроке, состоящей из нуля или более символов, [52] в то время как другие символы соответствуют только самим себе. Пререквизиты также используются %
, чтобы показать, как их имена связаны с целевым именем.
Приведенный выше пример правила суффикса будет выглядеть как следующее правило шаблона:
# Из %.html в %.txt %.txt : %. html lynx -dump $< > $@
Однострочные комментарии начинаются с символа решетки (#).
Некоторые директивы в make-файлах могут включать в себя другие make-файлы.
Продолжение строки обозначается обратной косой \
чертой в конце строки.
цель: компонент \ компонент Tab ↹команда; \ команда | \ конвейерная командаTab ↹Tab ↹
Make-файл:
ПАКЕТ = пакет ВЕРСИЯ = ` дата "+%Y.%m%d%" ` RELEASE_DIR = .. RELEASE_FILE = $( ПАКЕТ ) - $( ВЕРСИЯ ) # Обратите внимание, что переменная LOGNAME берется из среды # в оболочках POSIX. # # цель: все — цель по умолчанию. Ничего не делает. all : echo "Привет $( LOGNAME ) , по умолчанию нечего делать" # иногда: echo "Привет ${LOGNAME}, по умолчанию нечего делать" echo "Попробуйте 'сделать помощь'" # target: help — Отобразить вызываемые цели. помощь : egrep "^# target:" [ Mm ] akefile # target: list - Список исходных файлов list : # Не будет работать. Каждая команда находится в отдельной оболочке cd src ls. # Верно, продолжение той же оболочки cd src ; \ лс # target: dist — Выпустить релиз. dist : tar -cf $( RELEASE_DIR ) / $( RELEASE_FILE ) && \ gzip -9 $( RELEASE_DIR ) / $( RELEASE_FILE ) .tar
Ниже приведен очень простой make-файл, который по умолчанию (правило «все» указано первым) компилирует исходный файл с именем «helloworld.c» с использованием системного компилятора C, а также предоставляет «чистую» цель для удаления сгенерированных файлов, если пользователь желание начать все сначала. И $@
— $<
это два так называемых внутренних макроса (также известных как автоматические переменные), которые обозначают имя цели и «неявный» источник соответственно. В приведенном ниже примере $^
расширяется до списка предварительных условий, разделенного пробелами. Есть ряд других внутренних макросов. [49] [53]
CFLAGS ?= -g все : приветмир приветмир : приветмир . o # Команды начинаются с TAB без пробелов $( CC ) $( LDFLAGS ) -o $@ $^ helloworld.o : приветмир . c $( CC ) $( CFLAGS ) -c -o $@ $< очистить : FRC $( RM ) helloworld helloworld.o # Эта псевдоцель приводит к пересозданию всех целей, # которые зависят от FRC, даже если файл с именем цели существует. # Это работает с любой реализацией make при условии, # что в текущем каталоге нет файла FRC. ФРК :
Многие системы поставляются с предопределенными правилами Make и макросами для определения общих задач, таких как компиляция на основе суффикса файла. Это позволяет пользователям опустить фактические (часто непереносимые) инструкции о том, как сгенерировать цель из источника(ов). В такой системе приведенный выше make-файл можно изменить следующим образом:
все : приветмир приветмир : приветмир . o $( CC ) $( CFLAGS ) $( LDFLAGS ) -o $@ $^ очистить : FRC $( RM ) helloworld helloworld.o # Это явное правило суффикса. Его можно опустить в системах #, которые автоматически обрабатывают подобные простые правила. .co : $( CC ) $( CFLAGS ) -c $< FRC : .СУФФИКСЫ : . с
То, что «helloworld.o» зависит от «helloworld.c», теперь автоматически обрабатывается Make. В таком простом примере, как показанный здесь, это вряд ли имеет значение, но реальная сила правил суффиксов становится очевидной, когда количество исходных файлов в программном проекте начинает расти. Достаточно лишь написать правило для этапа компоновки и объявить объектные файлы обязательными. Затем Make неявно определяет, как создавать все объектные файлы, и ищет изменения во всех исходных файлах.
Простые правила суффиксов работают хорошо, пока исходные файлы не зависят друг от друга и от других файлов, таких как файлы заголовков. Другой способ упростить процесс сборки — использовать так называемые правила сопоставления с образцом, которые можно комбинировать с генерацией зависимостей с помощью компилятора. В качестве последнего примера, требующего компилятора gcc и GNU Make, вот общий make-файл, который компилирует все файлы C в папке в соответствующие объектные файлы, а затем связывает их с окончательным исполняемым файлом. Перед компиляцией зависимости собираются в формате, удобном для make-файла, в скрытый файл «.dependent», который затем включается в make-файл. Портативные программы должны избегать конструкций, используемых ниже.
# Общий файл GNUMakefile# Просто фрагмент, позволяющий прекратить выполнение других команд make(1), # которые не понимают эти строки ifneq (,) Для этого make-файла требуется GNU Make. конец PROGRAM = foo C_FILES := $( подстановочный знак *.c ) OBJS := $( patsubst %.c, %.o, $( C_FILES )) CC = cc CFLAGS = -Wall -pedantic LDFLAGS = LDLIBS = -lm все : $( ПРОГРАММА ) $(ПРОГРАММА) : . зависят $( OBJS ) $( CC ) $( CFLAGS ) $( OBJS ) $( LDFLAGS ) -o $( PROGRAM ) $( LDLIBS ) зависеть : . зависеть.dependent : cmd = gcc - MM - MF зависит $( var ) ; кот зависит >> . зависеть ;.dependent : @echo "Генерация зависимостей..." @ $( foreach var, $( C_FILES ) , $( cmd )) @rm -f depend -включить .зависить# Это правила сопоставления с образцом. В дополнение к используемым здесь автоматическим # переменным, в особых случаях может быть полезна переменная $*, которая соответствует символу %, обозначающему #. %.о : %. c $( CC ) $( CFLAGS ) -c $< -o $@ % : %. o $( CC ) $( CFLAGS ) -o $@ $< очистить : rm -f .dependent $( OBJS ) .PHONY : чистая зависимость
Makefile состоит из зависимостей, а забытый или дополнительный файл может быть не сразу очевиден для пользователя и может привести к тонким ошибкам в сгенерированном программном обеспечении, которые трудно обнаружить. Чтобы избежать этой проблемы и синхронизировать зависимости в исходном коде и make-файлах, можно использовать различные подходы. Один из подходов заключается в использовании компилятора для отслеживания изменений зависимостей. Например, GCC может статически анализировать исходный код и автоматически создавать правила для данного файла с помощью -MM
переключателя. Другой подход — это make-файлы или сторонние инструменты, которые будут генерировать make-файлы с зависимостями (например, набор инструментов Automake от GNU Project может делать это автоматически).
Другой подход — использовать инструменты мета-сборки, такие как CMake , Meson и т. д.
При общем обслуживании DWB мы использовали систему контроля исходного кода и утилиту make, предоставляемую интерактивной операционной системой PWB/UNIX*.
{{cite web}}
: CS1 maint: bot: исходный статус URL неизвестен ( ссылка )В make предусмотрены включение Makefile, условные структуры и циклы for, напоминающие язык программирования C.