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