Просто о make
Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание UNIX. И хотя UNIX давно уже нельзя назвать простой системой, да и минимализм в ней узреть не так то просто, ее можно считать наглядным примером количество- качественной трансформации множества простых и понятных вещей в одну весьма непростую и не прозрачную. В своем развитии make прошел примерно такой же путь: простота и ясность, с ростом масштабов, превратилась в жуткого монстра (вспомните свои ощущения, когда впервые открыли мэйкфайл).
Мое упорное игнорирование make в течении долгого времени, было обусловлено удобством используемых IDE, и нежеланием разбираться в этом ‘пережитке прошлого’ (по сути — ленью). Однако, все эти надоедливые кнопочки, менюшки ит.п. атрибуты всевозможных студий, заставили меня искать альтернативу тому методу работы, который я практиковал до сих пор. Нет, я не стал гуру make, но полученных мною знаний вполне достаточно для моих небольших проектов. Данная статья предназначена для тех, кто так же как и я еще совсем недавно, желают вырваться из уютного оконного рабства в аскетичный, но свободный мир шелла.
Make- основные сведения
make — утилита предназначенная для автоматизации преобразования файлов из одной формы в другую. Правила преобразования задаются в скрипте с именем Makefile, который должен находиться в корне рабочей директории проекта. Сам скрипт состоит из набора правил, которые в свою очередь описываются:
1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).
В общем виде синтаксис makefile можно представить так:
# Индентация осуществляется исключительно при помощи символов табуляции, # каждой команде должен предшествовать отступ : .
То есть, правило make это ответы на три вопроса:
---> [Как делаем? (команды)] --->
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:
---> [трансляция] --->
---> [линковка] --->
Простейший Makefile
Предположим, у нас имеется программа, состоящая всего из одного файла:
/* * main.c */ #include int main()
Для его компиляции достаточно очень простого мэйкфайла:
hello: main.c gcc -o hello main.c
Данный Makefile состоит из одного правила, которое в свою очередь состоит из цели — «hello», реквизита — «main.c», и команды — «gcc -o hello main.c». Теперь, для компиляции достаточно дать команду make в рабочем каталоге. По умолчанию make станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:
$ make
Компиляция из множества исходников
Предположим, что у нас имеется программа, состоящая из 2 файлов:
main.c
/* * main.c */ int main()
/* * hello.c */ #include void hello()
Makefile, выполняющий компиляцию этой программы может выглядеть так:
hello: main.c hello.c gcc -o hello main.c hello.c
Он вполне работоспособен, однако имеет один значительный недостаток: какой — раскроем далее.
Инкрементная компиляция
Представим, что наша программа состоит из десятка- другого исходных файлов. Мы вносим изменения в один из них, и хотим ее пересобрать. Использование подхода описанного в предыдущем примере приведет к тому, что все без исключения исходные файлы будут снова скомпилированы, что негативно скажется на времени перекомпиляции. Решение — разделить компиляцию на два этапа: этап трансляции и этап линковки.
Теперь, после изменения одного из исходных файлов, достаточно произвести его трансляцию и линковку всех объектных файлов. При этом мы пропускаем этап трансляции не затронутых изменениями реквизитов, что сокращает время компиляции в целом. Такой подход называется инкрементной компиляцией. Для ее поддержки make сопоставляет время изменения целей и их реквизитов (используя данные файловой системы), благодаря чему самостоятельно решает какие правила следует выполнить, а какие можно просто проигнорировать:
main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c hello: main.o hello.o gcc -o hello main.o hello.o
Попробуйте собрать этот проект. Для его сборки необходимо явно указать цель, т.е. дать команду make hello.
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.
После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.
Фиктивные цели
На самом деле, в качестве make целей могут выступать не только реальные файлы. Все, кому приходилось собирать программы из исходных кодов должны быть знакомы с двумя стандартными в мире UNIX командами:
$ make $ make install
Командой make производят компиляцию программы, командой make install — установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:
- all — является стандартной целью по умолчанию. При вызове make ее можно явно не указывать.
- clean — очистить каталог от всех файлов полученных в результате компиляции.
- install — произвести инсталляцию
- uninstall — и деинсталляцию соответственно.
.PHONY: all clean install uninstall all: hello clean: rm -rf hello *.o main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c hello: main.o hello.o gcc -o hello main.o hello.o install: install ./hello /usr/local/bin uninstall: rm -rf /usr/local/bin/hello
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно — получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать. Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
$ make clean $ make
Для выполнения целей install/uninstall вам потребуются использовать sudo.
Переменные
Все те, кто знакомы с правилом DRY (Don’t repeat yourself), наверняка уже заметили неладное, а именно — наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:
SRC = main.c hello.c
Так мы определили список исходных файлов. Для использования значения переменной ее следует разименовать при помощи конструкции $(); например так:
gcc -o hello $(SRC)
Ниже представлен мэйкфайл, использующий две переменные: TARGET — для определения имени целевой программы и PREFIX — для определения пути установки программы в систему.
TARGET = hello PREFIX = /usr/local/bin .PHONY: all clean install uninstall all: $(TARGET) clean: rm -rf $(TARGET) *.o main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c $(TARGET): main.o hello.o gcc -o $(TARGET) main.o hello.o install: install $(TARGET) $(PREFIX) uninstall: rm -rf $(PREFIX)/$(TARGET)
Это уже посимпатичней. Думаю, теперь вышеприведенный пример для вас в особых комментариях не нуждается.
Автоматические переменные
Автоматические переменные предназначены для упрощения мейкфайлов, но на мой взгляд негативно сказываются на их читабельности. Как бы то ни было, я приведу здесь несколько наиболее часто используемых переменных, а что с ними делать (и делать ли вообще) решать вам:
- $@ Имя цели обрабатываемого правила
- $ < Имя первой зависимости обрабатываемого правила
- $^ Список всех зависимостей обрабатываемого правила
Заключение
В этой статье я попытался подробно объяснить основы написания и работы мэйкфайлов. Надеюсь, что она поможет вам приобрести понимание сути make и в кратчайшие сроки освоить этот провереный временем инструмент.
Make run папка что это
Этот раздел описывает соглашения по написанию Make-файлов для программ GNU.
Каждый Makefile должен включать строку
SHELL = /bin/sh
для того, чтобы избежать проблем на системах, в которых переменная SHELL может быть унаследована из окружения. (Подобные сложности отсутствуют при использовании утилиты GNU make).
Не следует предполагать, что ‘.’ входит в путь для поиска исполнимых программ. Когда Вам нужно запускать программы, которые являются частью Вашего пакета, во время работы make, необходимо использовать префикс ‘./’, если программа строится в ходе работы make, или ‘$(srcdir)/’ если файл является неизменяемой частью исходных текстов. Когда ни один из этих префиксов не указан, выполняется поиск в текущем пути.
Отличие между ‘./’ и ‘$(srcdir)’ существенно, когда используется опция —srcdir программы configure. Правило вида:
foo.1: foo.man sedscript sed -e sedscript foo.man > foo.1
не будет работать, если текущий каталог не является каталогом, содержащим исходные тексты, поскольку foo.man и sedscript в этом случае не находятся в текущем каталоге.
Когда используется GNU make, поиск исходных файлов может быть выполнен с помощью переменной VPATH, в случае, если в правиле присутствует только один файл зависимости. В этом случае автоматическая переменная make ‘$
foo.o: bar.c $(CC) -I. -I$(srcdir) $(CFLAGS) -c bar.c -o foo.c
должен быть записан следующим образом:
foo.c: bar.c $(CC) $(CFLAGS) $< -o $@
для того, чтобы VPATH мог бы быть правильно использован. Когда цель имеет несколько зависимостей, следует явно использовать $(srcdir). Например, приведенное выше в качестве примера правило для цели foo.1 следует записать следующим образом:
foo.1: foo.man sedscript sed -s $(srcdir)/sedscript $(srcdir)/foo.man > foo.1
Команды, которые пишутся в Makefile (как, впрочем, и в любых других shell-скриптах вроде configure), должны работать в sh, а не в csh. Не следует использовать каких-либо специальных особенностей ksh или bash.
Скрипт configure и правила Makefile для построения и установки программы не должны использовать никаких утилит кроме следующих:
cat cmp cp echo egrep expr grep ln mkdir mv pwd rm rmdir sed test touch
Следует использовать только общепринятые опции для этих программ. К примеру, не следует использовать 'mkdir -p', поскольку эта опция хоть и удобна, но большинство систем не поддерживают ее.
Правила Makefile для построения и установки могут также использовать компиляторы и другие необходимые программы, но их использование должно выполняться через make-переменные для того, чтобы пользователь имел возможность заменить их определение на собственную альтернативу. Вот некоторые из программ, которые мы имеем ввиду:
ar bison cc flex install ld lex make makeinfo ranlib texi2dvi yacc
Когда Вы используете ranlib, Вы должны проверять его существование в системе, и использовать его только в том случае, если он присутствует. Это необходимо для того, чтобы можно было выполнять построение на системах, не имеющих ranlib.
Если Вы используете символические ссылки, Вы должны поддержать возможность использования Вашего пакета в системах, которые не поддерживают символических ссылок.
Возможно использование других утилит во фрагментах Makefile или скриптах, которые предназначены только для использования в данной конкретной системе, и для которых Вы уверены в их существовании.
Все программы в GNU должны иметь следующие цели в своих Makefile'ах. 'all'
Компиляция всей программой. Эта цель должна быть целью по умолчанию. Эта цель не должна перестраивать никакие файлы документации; info-файлы должны включаться в поставку, DVI файлы должны формироваться только по явному запросу.
Компиляция программы и копирование исполнымых файлов, библиотек и т.д. туда, где они должны располагаться для их обычного использования. Если возможно, по этой цели должна выполняться простая проверка того, что программа была правильно установлена.
Команды должны создать все каталоги, в которых файлы будут установлены, если эти каталоги уже не существуют. Сюда входят каталоги, указанные как значения переменных prefix и exec_prefix, так же, как и все их требуемые подкаталоги. Другой способ выполнить это подразумевает использование цели installdirs, описанной ниже.
Используйте символ '-' перед любой командой для установки файлов с man-страницами для того, чтобы игнорировались все ошибки. Это нужно для того, чтобы можно было устанавливать программу на систему, в которой не установлена Unix-система man-документов.
Для установки info-файлов необходимо скопировать их в $(infodir) с $(INSTALL_DATA) (см. Переменные для исполнения команд), и затем выполнить программу install-info (если она имеется). install-info это скрипт, который выполняет редактирование файла 'dir' системы Info для того, чтобы добавить или обновить элемент меню для данного Info-файла; этот скрипт является частью пакета Texinfo. Далее приводится пример правила для установки Info-файла:
$(infodir)/foo.info: foo.info # There may be a newer info file in . than in srcdir -if test -f foo.info; then d=.; \ else d=$(srcdir); i; $(INSTALL_DATA) $$d/foo.info $@; \ # Run install-info only if it exists. # Use 'if' instead of just prepending '-' to the # line so we notice real errors from install-info. # We use '$(SHELL) -c' because some shells do # fail gracefuly when there is an unknown command. if $(SHELL) -c 'install-info --version' \ >/dev/null 2>&1; then \ install-info --infodir=$(infodir) $$d/foo.info; \ else true; fi
Выполняется удаление всех установленных файлов, созданных при исполнении цели 'install' (но не тех файлов, которые создаются при исполнении цели 'all').
Выполняется удаление тех файлов из текущего каталога, которые были созданы при построении программы. Не удаляются файлы, в которых сохранена конфигурация. Так же сохраняются файлы, которые могут быть получены при построении, но которые тем не менее входят в поставку. Следует удалять .dvi файлы, если они не являются частью поставки.
Удаляются все файлы из текущего каталога, которые были созданы при конфигурировании или построении программы. Если Вы распакуете исходные тексты программ, после чего построите программу не создавая самостоятельно каких-либо файлов, то 'make distclean' должно оставить только те файлы, которые входили в поставку.
Работает так же, как и clean, но оставляет неудаленными некоторые файлы, которые обычно нежелательно перекомпилировать. Например, цель 'mostyclean' для GCC не удаляет файл 'libgcc.a', поскольку его перекомпиляция редко когда бывает нужна, и к тому же занимает много времени.
Выполняется удаление из текущего каталога всего, что может быть построено с помощью Makefile. Обычно это включает в себя все то, что удаляется по distclean, исходные файлы на C, полученные с помощью построителя синтаксических анализаторов Bison, таблицу тегов, info-файлы и т.д.
Имеется одно исключение: 'make realclean' не должен удалять 'configure', даже если 'configure' может быть построен используя правило в Makefile. Более того, 'make realclean' не должен удалять ничего из того, чье существование требуется для выполнения 'configure' и начального исполнения программы.
Обновляет таблицу тегов для программы.
Выполняется построение всех требуемых info-файлов. Лучший всего написать правила по следующему образцу:
info: foo.info foo.info: foo.texi chap1.texi chap2.texi $(MAKEINFO) $(srcdir)/foo.texi
Вы должны определить в Makefile переменную MAKEINFO. Она должна запускать программу makeinfo, которая входит в поставку пакета Texinfo.
Выполняется построение DVI-файлов для всей TeXinfo-документации. Пример правил:
dvi: foo.dvi foo.dvi: foo.texi chap1.texi chap2.texi $(TEXI2DVI) $(srcdir)/foo.texi
Вы должны определить переменную TEXI2DVI в Makefile. Она должна запускать программу texi2dvi, которая является частью поставки пакета Texinfo. Можно указать просто зависимости, тогда GNU Make сам предоставит эту команду.
Выполняется создание tar-файла, содержащего дистрибутивную поставку этой программы. tar-файл должен быть создан таким образом, чтобы имена файлов в нем начинались с подкаталога, имя которого являлось бы именем поставляемого пакета. Имя может включать в себя номер версии.Например, поставка дистрибутивного архива для GCC версии 1.40 должна распаковываться в каталог с именем 'gcc-1.40'.
Простейший способ выполнить это состоит в создании названного таким образом каталога, и использовании ln или cp для установки соответствующих файлов в него. После чего необходимо выполнить tar для этого подкаталога.
Цель dist должна явно зависеть от всех файлов, которые не являются исходными, но должны входить в поставку, для того, чтобы убедиться в их актуальности. Смотри раздел "Издание версий". Выполняет самотестирование (если предусмотрено). Пользователь должен построить программу перед запуском тестов, но не должен устанавливать программу; Вы должны написать тесты таким образом, чтобы они работали когда программа построена, но не установлена.
Следующие цели в тех программах, в которых они нужны, должны иметь следующие стандартные имена. Выполняет проверку правильности установки (если соответствующие тесты предусмотрены). Пользователь должен построить и установить программу перед запуском этих тестов. Вы не должны считать, что $(bindir) входит в путь поиска программ. Полезно добавить цель с именем 'installdirs' для создания структуры каталогов, в которых будут установлены файлы. Имеется скрипт, названный 'mkinstalldirs', который подходит для этой цели. Он находится в пакете Texinfo. Вы можете написать правило по следующему образцу: # Make sure all installation directories (e.g. $(bindir)) # actually exists by making them if necessary. installdirs: mkinstalldirs $(srcdir)/mkinstalldirs $(bindir) $(datadir) \ $(libdir) $(infodir) \ $(mandir)
Makefile должен предоставлять переменные для того, чтобы можно было перекрыть некоторые команды, опции и т.д.
В частности, Вы должны запускать большинство утилит через переменные. Так, если Вы используете Bison, введите переменную с именем BISON, чье значение по умолчанию установлено как 'BISON=bison', и ссылайтесь на нее $(BISON) везде, где Вам нужно использовать Bison.
Утилиты управления файлами (такие, как ln, rm, mv и т.д.) не должны выполняться через переменные, поскольку пользователям нет необходимости заменять их другими программами.
Каждой переменной с именем программы должна соответствовать переменная с опциями, которые должны передаваться этой программе. Следует добавлять 'FLAGS' к имени переменной для программы, чтобы получить имя переменной для опций - например, BISONFLAGS. (Имя CFLAGS является исключением из этого правила, но мы сохраняем его, поскольку оно общепринято.) Используйте CPPFLAGS в любой команде компиляции, которая запускает препроцессор, и LDFLAGS в любой команде компиляции, которая выполняет редактирование связей, так же, как и при явном использовании ld.
Если имеются опции C-компилятора, которые должны быть использованы для правильной компиляции некоторых файлов, не включайте их в CFLAGS. Пользователи ожидают, что они могут свободно установить CFLAGS сами. Вместо этого, упорядочите передачу таких опций компилятору независимо от CFLAGS, указывая их явно в командах компиляции или определяя неявное правило как здесь:
CFLAGS = -g ALL_CFLAGS = -I. $(CFLAGS) .c.o: $(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) $Не следует включать опцию -g в CFLAGS, поскольку она не требуется для правильной компиляции. Вы можете рассматривать это умолчание как рекоменуемое. Если пакет установлен так, что он компилируется с использованием GCC по умолчанию, то вы можете включить '-O' в умалчиваемое значение переменной CFLAGS.
Помещайте CFLAGS последним в команде компиляции, после всех других переменных, содержащих опции компилятора, для того, чтобы пользователь мог использовать CFLAGS для перекрытия опций, указанных в других переменных.
Каждый Makefile должен определять переменную INSTALL, которая обозначает бызовою команду для установки файла в системе.
Каждый Makefile должен также определять переменные INSTALL_PROGRAM и INSTALL_DATA. (Умолчание для этих переменных должно быть $(INSTALL).) В дальнейшем, эти переменные должны использоваться для установки соответственно исполнимых и неисполнимых файлов. Используйте эти переменные как в примере:
$(INSTALL_PROGRAM) foo $(bindir)/foo $(INSTALL_DATA) libfoo.a $(libdir)/libfoo.aСледует всегда указывать в качестве второго аргумента имя файла (не имя каталога). Надо использовать отдельную команду для каждого устанавливаемого файла.
Каталоги для установки должны всегда именоваться посредством переменных, поскольку это упрощает установку программы в нестандартное место. Стандартные имена для таких переменных следующие: 'prefix'
Префикс, используемый для построения умолчательных значений для переменных, перечисленных ниже. Значение по умолчанию для переменной prefix должно быть '/usr/local' (по крайней мере сейчас).
Префикс, используемый при построении значений по умолчанию для некоторых переменных, перечисленных ниже. Значение по умолчанию для переменной exec_prefix должно быть $(prefix).
Как правило, $(prefix) используется для каталогов, которые содержат машиннозависимые файлы (такие, как исполнимые файлы и библиотеки процедур), в то время как $(prefix) используется для остальных каталогов.
Каталог для установки исполняемых файлов программ, которые могут быть запущены пользователем. Обычно, это '/usr/local/bin', но должно быть записано как '$(exec_prefix)/bin'
Каталог для установки исполняемых файлов, которые будут запускаться другими программами, а не пользователем. Объектные файлы и библиотеки объектного кода должны так же попадать в этот каталог. Идея состоит в том, что этот каталог используется для файлов, которые зависят от конкретной архитектуры машины, но не должны находится в пути для команд. Значение для libdir обычно '/usr/local/lib', но должно быть записано как '$(exec_prefix)/lib'.
Каталог для установки файлов с неизменяемыми данными, которые используются программами во время их работы. Этот каталог используется для файлов, которые не зависят от используемого типа машины. Значение этой переменной обычно '/usr/local/lib', но должно быть записано как '$(prefix)/lib'.
Каталог для установки файлов с данными, которые программы могут изменять в процессе своей работы. Эти файлы должны быть независимыми от типа используемой машины, и должны допускать разделение их между машинами при сетевой установке. Значение этой переменной обычно '/usr/local/lib', но должно быть записано как '$(prefix)/lib'
Каталог для установки заголовочных файлов (header-файлов), которые могут быть включены другими пользовательскими программами с помощью директивы препроцессора '#include'. Значение этой переменной обычно '/usr/local/include', но должно быть записано как '$prefix/include'.
Большинство компиляторов отличных от GCC не выполняют поиск заголовочных файлов в '/usr/local/include', поэтому установка заголовочных файлов в этот каталог целесообразна только для GCC. Иногда это не представляет из себя проблему, так как некоторые библиотеки предназначены для использования исключительно с GCC. Но имеются так же и библиотеки, предназначенные для работы и с другими компиляторами. Они должны устанавливать свои включаемые файлы в два места, одно из которых определено переменной includedir, а другое oldincludedir.
Каталог для установки заголовочных файлов для использования с компиляторами, отличными от GCC. Значение этой переменной обычно '
Команды Makefile должны проверить, не пусто ли значение переменной oldincludedir. Если оно пусто, они не должны пытаться использовать ее и выполнять повторную установку включаемых файлов.
Пакет не должен замещать существующие заголовочные файлы в этом каталоге, в случае, если заголовочный файл пришел не из того же пакета. Так, если Ваш пакет Foo предоставляет заголовочный файл 'foo.h', то он должне установить заголовочный файл в каталог, заданный oldincludedir, если (1) foo.h не существует, или (2) foo.h существует и пришел из пакета Foo.
Для того, чтобы проверить, что foo.h пришел из пакета Foo, поместите специальную строку в этот файл - часть комментария - и проверьте наличие этой строки с помощью команды grep.
Каталог для установки man-страниц (если они есть) для этого пакета. Переменная должна включать суффикс для соответсвующей секции руководства - обычно '1' для утилит. Обычно значение этой переменной '/usr/local/man/man1', но должно быть записано как '$(prefix)/man/man1'
Каталог для установки в раздел 1 man-страниц.
Каталог для установки в раздел 2 man-страниц.
Используйте переменные такого рода вместо 'mandir', если пакет должен устанавливать man-страницы более чем в один раздел.
Расширение для имени файла для устанавливаемых man-страниц. Переменная должна содержать точку, за которой следует соответствующая цифра, обычно '.1'.
Расширение имени файла для установки в раздел 1 man-страниц.
Расширение имени файла для установки в раздел 2 man-страниц.
Используйте переменные такого рода вместо 'manext', если пакет должен устанавливать man-страницы более чем в один раздел.
Переменная должна содержать имя каталога для установки info-файлов для данного пакета. По умолчанию, ее значение должно быть '/usr/local/info', но должно быть записано как '$(prefix)/info'.
В этой переменной должно находится имя каталога, содержащего компилируемые исходные тексты. Значение этой переменной обычно вставляется скриптом configure. Пример:
# Common prefix for installation directories. # NOTE: This directory must exist when you start the install. prefix = /usr/local exec_prefix = $(prefix) # Where to put the executable for the command 'gcc'. bindir = $(exec_prefix)/bin # Where to put the directories used by the compiler. libdir = $(exec_prefix)/lib # Where to put the Info files. infodir = $(prefix)/infoЕсли Ваша программа устанавливает большое количество файлов в один из стандартных каталогов, указанных пользователем, целесообразно сгруппировать их в один подкаталог, относящийся к этой программе. Если Вы делаете это, Вы должны создать такие подкаталоги в правиле для цели install.
Не ожидайте от пользователя указания имени такого подкаталога в заданном им значении переменных, перечисленных выше. Наличие однообразного набора имен переменных для каталогов, в которые будет установлена программа, позволяет пользователю указать точно такие же значения для нескольких различных GNU-пакетов. Для того, чтобы это было полезным, все пакеты должны быть разработаны таким образом, чтобы они правильно использовали значения переменных.
Make run папка что это
make-файл, определяющий, как перекомпилировать программу, может быть использован более, чем одним способом. Самое простое использование заключается в перекомпиляции каждого файла, зависимости которого были обновлены. Обычно make-файлы пишутся так, чтобы при запуске программы make без аргументов она делала именно это. Но вы могли бы захотеть обновить только некоторые из файлов, вы могли бы захотеть использовать другой компилятор или другие опции компилятора, вы могли бы захотеть просто выяснить, у какие файлы нуждаются в обновлении, не изменяя их. Передавая программе make аргументы при ее запуске, вы можете сделать все это, а также много другого. Возвращаемым результатом программы make всегда является одно из трех значений: 0 Возвращаемым результатом является нуль, если программа make выполнилась успешно 2 Возвращаемым результатом является двойка, если программа make обнаруживает какую-либо ошибку. Она выдаст на экран сообщение, описывающее, какая именно ошибка произошла. 1 Возвращаемым результатом является единица, если вы используете опцию '-q' и программа make определяет, что какая-то цель не является, на момент запуска программы make, обновленной. Смотрите раздел 9.3 [Вместо исполнения команд].
Для определения имени make-файла служит опция '-f' или '--file' (работает также опция '--makefile'). Например, фрагмент командной строки '-f altmake' указывает на использование в качестве make-файла файла с именем 'altmake'. Если вы используете опцию '-f' несколько раз, указывая за каждым ее вхождением аргумент, все определенные таким образом файлы совместно используются в качестве make-файлов. Если вы не используете опцию '-f' или '--file', то в качестве имен по умолчанию пробуются имена 'GNUmakefile', 'makefile' и 'Makefile', именно в таком порядке, и в роли make-файла используется первый из этих трех, который существует или может быть порожден (смотрите главу 3 [Написание make-файлов]).
Главные цели представляют собой цели, которые в конечном счете стремится обновить программа make. Другие цели обновляются только в том случае, если они появляются в качестве зависимостей гланых целей или в качестве зависимостей зависимостей главных целей, и т.д.. По умолчанию, главной целью является первая цель в make-файле (не считая целей, начинающихся с точки). Следовательно, make-файлы обычно пишутся так, чтобы первой целью была цель для компиляции всей программы или всех программ, которые они описывают. Если первое правило make-файла имеет несколько целей, то только первая цель правила, а не весь список, становится главной целью по умолчанию. Вы можете определить другую главную цель или главные цели при помощи аргументов программы make. Используйте имя главной цели как аргумент. Если вы определяете несколько главных целей, то программа make обрабатывает каждую из них по очереди, в том порядке, в котором вы их упоминаете. Любая цель make-файла может быть определена в качестве главной цели (если она не начинается с символа '-' и не содержит символ '=', так как в этих случаях это будет воспринято, соответственно, как опция или определение переменной). В качестве главной цели может быть определена даже цель, не содержащаяся в make-файле, если программа make может найти неявное правило, которое указывает, как ее породить. Одно из использований определения главной цели возможно в том случае, если вы хотите откомпилировать только часть программы или только одну из нескольких программ. Определите в качестве главной цели каждый файл, который вы хотите заново породить. Например, рассмотрим каталог, содержащий несколько программ, с make-файлом, который начинается примерно так:
.PHONY: all all: size nm ld ar asЕсли вы работаете над программой size, то вы могли бы захотеть написать в качестве команды 'make size', и при этом были бы перекомпилированы только файлы этой программы. Еще одно использование определения главной цели состоит в порождении файлов, который при работе с главной целью по умолчанию не породились. Например, это может быть файл с отладочной информацией или версия программы, компилирующаяся специально для тестирования, которые могут иметь правило в make-файле, но не являться зависимостью главной цели про умолчанию. Определение главной цели также может использоваться при запуске команд, связанных с целями-именами действий (смотрите раздел 4.4 [Цели-имена действий]) или пустыми целями (смотрите раздел 4.6 [Пустые целевые файлы для фиксации событий]). Многие make-файлы содержат цель-имя действия и именем 'clean', которая удаляет все, за исключением исходных файлов. Естественно, это делается только в том случае, если вы явно требуете этого при помощи 'make clean'. Ниже приведен список типичных имен целей-имен действий и пустых целей. Смотрите раздел 14.3 [Стандартные цели], для детального списка всех стандартных имен целей, которые используются программными пакетами GNU. 'all' Порождает все цели верхнего уровня, которые упомянуты в make-файле 'clean' Удаляет все файлы, которые обычно создаются в результате выполнения программы make. 'mostlyclean' Аналогично цели 'clean', но может воздержаться от удаления некоторых файлов, перекомпилировать которые обычно не хочется. Например, в GCC цель 'mostlyclean' не удаляет файл 'libgcc.a', поскольку ее перекомпиляция требуется редко и занимает много времени. 'distclean' 'realclean' 'clobber' Любая из этих целей могла бы быть определена для удаления некоторых файлов, которые не удаляются при помощи цели 'clean'. Например, таким способом могли бы удаляться конфигурационные файлы или связи, которые обычно создаются в качестве подготовки к компиляции, даже если сам make-файл не может создать эти файлы. 'install' Копирует исполняемый файл в каталог, в котором пользователи обычно ищут команды, копирует все вспомогательные файлы, которые используются исполняемым файлом, в тот каталог, в котором он их ищет. 'print' Печатает список измененных исходных файлов. 'tar' Создает из исходных файлов tar-файл. 'shar' Создает из исходных файлов архив командной оболочки (shar-файл). 'dist' Создает из исходных файлов распространяемый файл. Это может быть tar-файл или shar-файл, или же сжатая версия одного из них, или даже что-нибудь еще, не упомянутое здесь. 'TAGS' Обновляет таблицу тэгов программы, получаемой при помощи данного make-файла. 'check' 'test' Выполняет самотестирование программы, получаемой при помощи данного make-файла.
- Если вы также используете опцию '-n' или '-q', вы можете увидеть, что делала бы программа make, если бы вам потребовалось изменить некоторые файлы.
- Без опций '-n' и '-q', когда программа make на самом деле выполняет команды, опция '-W' может заставить программу make действовать так, как если бы некоторые файлы были изменены, без реального изменения этих файлов.
Иногда у вас может быть иметься измененный исходный файл, но при этом вы можете не захотеть перекомпилировать все файлы, которые зависят от него. Например, предположим, что вы добавляете макрос или объявление в заголовочный файл, от которого зависят многие другие файлы. Следуя установленному порядку, программа make предполагает, что любое изменение в заголовочном файле требует перекомпиляции всех зависимых файлов, но вы знаете, что они не обязательно должны быть перекомпилированы и вы бы предпочли не тратить время, ожидая выполнения их компиляции.
- Используйте команду make для перекомпиляции исходных файлов, которые на самом деле требуют перекомпиляции.
- Сделайте изменения в исходных файлах.
- Используйте команду 'make -t' для отметки всех объектных файлов как обновленных. Когда вы в следующий раз запустите программу make, изменения в заголовочных файлах не вызовут никакой перекомпиляции.
- Перекомпилируйте исходные файлы, которые нуждаются в компиляции по причинам, не зависящим от определенного заголовочного файла, при помощи команды 'make -o '. Если речь идет о нескольких заголовочных файлах, используйте отдельную опцию '-o' для каждого заголовочного файла.
- Измените время обновления всех объектных файлов при помощи команды 'make -t'.
Аргумент, который содержит символ '=', определяет значение переменной: аргумент 'v=x' устанавливает x в качестве значения переменной v. Если вы определяете значение таким образом, то в make-файле все обычные присваивания значения этой же переменной игнорируются - мы говорим, что они перекрываются аргументом командной строки.
Наиболее типичным использованием этой возможности является передача дополнительных опций компиляторам. Например, в грамотно написанном make-файле в каждую команду, которая запускает C-компилятор, включается переменная CFLAGS, поэтому файл 'foo.c' должен компилироваться примерно так:
cc -c $(CFLAGS) foo.c
Таким образом, в какое значение вы ни устанавливаете переменную CFLAGS, это воздействует на каждый сеанс компиляции. make-файл, возможно, определяет обычное значение для переменной CFLAGS, например так:
CFLAGS=-g
Каждый раз, когда вы запускаете программу make, вы, если хотите, можете перекрыть это значение. Например, если вы напишете в командной строке 'make CFLAGS='-g -O'', каждый раз C-компиляция будет выполняться при помощи командной строки 'cc -c -g -O'. (Это иллюстрирует то, как вы можете использовать в командной оболочке апострофы для включения в значение переменной, при ее перекрытии, пробелов и других специальных символов.)
Переменная CFLAGS - это только одна их многих стандартных переменных, которые существуют только для того, чтобы вы могли изменять их таким образом. Смотрите раздел 10.3 [Переменные, используемые неявными правилами], где приведен полный их список.
Вы можете также написать make-файл, работа которого зависела бы от дополнительных переменных, введенных в употребление вами, что дало бы пользователю возможность управлять другими аспектами работы make-файла путем изменения переменных.
Когда вы перекрываете переменную при помощи аргумента командной строки, вы можете определить либо рекурсивно вычисляемую переменную, либо упрощенно вычисляемую переменную. В примерах, показанных выше, создается рекурсивно вычисляемая переменная; для создания упрощенно вычисляемой переменной, пишите ':=' вместо '='. Но если вы не хотите включать в определяемое вами значение ссылку на переменную или вызов функции, то становится безразличным, какого вида переменную вы создаете.
Если один способ, при помощи которого make-файл может изменить переменную, которую вы перекрыли. Он заключается в использовании директивы override, которая представляет собой строку, которая выглядит примерно так: 'override VARIABLE = VALUE' (смотрите раздел 6.7 [Директива override]).
Обычно, когда происходит ошибка при выполнении команды командной оболочки, программа make немедленно прекращает работу, возвращая ненулевой результат. Никаких команд ни для какой цели после этого не выполняется. Ошибка подразумевает, что главная цель не может быть корректно заново порождена, и программа make, как только узнает об ошибке, сразу сообщает об этом.
Когда вы компилируете программу, которую вы только что изменили, это - не то, что вы хотите. Вместо этого, вы бы предпочли, чтобы программа make попробовала откомпилировать каждый файл, который позволяет это сделать, чтобы показать вам как можно больше ошибок компиляции.
В таких случаях вам следует использовать опцию '-k' или '--keep-going'. Она указывает программе make продолжать работу с другими зависимостями обновляемых целей, при необходимости заново порождая эти зависимости, перед прекращением работы возвратом ненулевого результата. Например, в случае ошибки при компиляции одного объектного файла, программа make, запущенная при помощи командной строки 'make -k', продолжит компиляцию других объектных файлов, хотя уже и известно, что их компоновка будет невозможна. Помимо продолжения работы после после неудачной команды командной оболочки, программа make, запущенная при помощи командной строки 'make -k', будет продолжать работу как можно дольше после того, как она выяснит, что не знает, как породить целевой файл или файл зависимости. Такая ситуация всегда будет вызывать сообщение об ошибке, но без опции '-k' это является фатальной ошибкой (смотрите раздел 9.7 [Обзор опций]).
Обычное поведение програмы make предполагает, что вы стремитесь получить обновленные главные цели - как только программа make выясняет, что это невозможно, она также может сразу сообщить о неудаче в своей работе. Опция '-k' указывает на то, что на самом деле программа make используется для проверки как можно большего количества изменений, сделанных в программе, возможно, для поиска нескольких независимых проблем для того, чтобы вы могли исправить их перед следующей попыткой компиляции. Именно поэтому M-x compile, команда редактора Emacs, по умолчанию передает программе make опцию '-k'.
Вот список всех опций, которые понимает программа make: '-b'
Эти опции включены для совместимости с другими версиями программы make и они игнорируются.
Перед чтением make-файлов переходит в каталог, определяемый аргументом . Если определены несколько опций '-C', то каждая из них интерпретируется относительно предыдущей: фрагмент командной строки '-C / -C etc' эквивалентен такому фрагменту: '-C /etc'. Это обычно используется при рекурсивных вызовах программы make. (смотрите раздел 5.6 [Рекурсивное использование программы make]).
Помимо обычной работы, выводит отладочную информацию. Отладочная
информация говорит о том, какие файлы рассматриваются на предмет
порождения их заново, времена изменения каких файлов
сравниваются и с какими результатами, какие файлы на самом деле
нуждаются в порождении их заново, какие неявные правила
принимаются в расчет и какие из них применяются - все, что имеет
отношение к тому, как программа make решает, что делать.
Дает переменным, взятым из командной среды, приоритет перед переменными из make-файлов. Смотрите раздел 6.9 [Переменные из командной среды].
Читает в качестве make-файла файл с именем, определяемым аргументом . Смотрите главу 3 [Написание make-файлов].
Напоминает вам об опциях, которые понимает программа make, после чего заканчивает работу.
Игнорирует все ошибки в командах, выполняемых для порождения файлов заново. Смотрите раздел 5.4 [Ошибки в командах].
Назначает каталог, определяемый аргументом для поиска включаемых make-файлов. Смотрите раздел 3.3 [Включение других make-файлов]. Если если используется несколько опций '-I' для определения нескольких каталогов, поиск по каталогам происходит в том порядке, в котором они перечислены.
Определяет количество заданий (команд), которые будут выполняться одновременно. При отсутствии аргумента, программа make запускает одновременно столько заданий, сколько возможно. Если определена более, чем одна опция '-j', то действует та, которая указана последней в командной строке. Смотрите раздел 5.3 [Параллельное выполнение], для дополнительной информации о том, как выполняются команды.
Продолжает работу после ошибки как можно дольше. В то время как порождение цели закончилось неудачно, и цели, зависящие от нее, не могут быть заново порождены, другие зависимости этих целей все равно могут быть обработаны. Смотрите раздел 9.6 [Проверка компиляции программ].
Определяет, что ни одно новое задание (команда) не должно стартовать, если есть другие запущенные задания и средняя загрузка не меньше, чем значение, определяемое аргументом (число с плавающей точкой). Отсутствие аргумента отменяет действие предыдущего предела загрузки.
Печатает команды, который должны выполняться, но не выполняет их. Смотрите раздел 9.3 [Вместо исполнения команд].
Не порождает заново файл с именем, определяемым аргументом , даже если он более старый, чем его зависимости, и не порождает заново ничего, что заново порождалось бы в случае изменений в этом файле. По существу, файл обрабатывается как очень старый и его правила игнорируются. Смотрите раздел 9.4 [Предотвращение перекомпиляции некоторых файлов].
Выводится на экран базу данных (правила и значения переменных), которая получается в результате чтения make-файлов, а затем выполняется как обычно или каким-либо другим образом, если это определено. При использовании этой опции также выводится на экран информация о версии, которая дается с помощью опции '-v' (смотрите ниже). Чтобы вывести на экран базу данных, не пытаясь породить заново никаких файлов, используйте команду 'make -p -f /dev/null'.
"Режим запроса". Не выполняет никаких команд и ничего не печатает - просто возвращает результат, который является нулем, если указанные цели уже обновлены, единицей, если требуется какое-либо новое порождение или двойкой, если обнаруживается ошибка. Смотрите раздел 9.3 [Вместо исполнения команд].
Отменяет использование встроенных неявных правил (смотрите главу 10 [Использование неявных правил]). Вы можете при этом определить ваши собственные неявные правила путем написания шаблонных правил (смотрите раздел 10.5 [Определение и переопределение шаблонных правил]). Опция '-r' также очищает список суффиксов, по умолчанию используемый для суффиксных правил (смотрите раздел 10.7 [Устаревшие суффиксные правила]). Но вы можете при этом, с помощью правила для цели .SUFFIXES, определить ваши собственные суффиксы и затем определить ваши собственные суффиксные правила.
Работа без сообщений - не выводит на экран команды в момент их исполнения.
Отменяет действие опции '-k'. Эта опция не нужна никогда, за исключением рекурсивного использования программы make, когда опция '-k' может быть унаследована от процесса make верхнего уровня через переменную MAKEFLAGS (смотрите раздел 5.6 [Рекурсивное использование программы make]), а также установки вами в вашей командной среде опции '-k' в переменную MAKEFLAGS.
Изменение времени обновления файлов (пометка их как обновленных без реального их изменения) вместо запуска соответствующих им команд. Это используется в целях создания иммитации того, что команды выполнены, для того, чтобы обмануть будущие вызовы программы make. Смотрите раздел 9.3 [Вместо исполнения команд].
Выводит на экран версию программы make, а также ее copyright, список авторов и замечание об отсутствии гарантии - после этого работа завершается.
Выводит на экран сообщение, содержащее имя рабочего каталога как перед, так и после выполнения make-файла. Это может быть полезным для отслеживания ошибок из сложным образом вложенных рекурсивных команд make. Смотритие раздел 5.6 [Рекурсивное использование программы make]. (На практике вам редко придется определять эту опцию, так как программа make делает это за вас - смотрите раздел 5.6.4 [Опция '--print-directory']).
Отменяет вывод на экран имени рабочего каталога, осуществляемый с помощью опции '-w'. Этот способ полезен тогда, когда опция '-w' устанавливается автоматически, но вы не хотите видеть лишних сообщений. Смотрите раздел 5.6.4 [Опция '--print-directory'].
Делает вид, что цель, определяемая аргументом , только что была изменена. При использовании вместе с опцией '-n', эта опция показывает вам, что бы произошло, если бы вам потребовалось изменить этот файл. Без опции '-n', эта опция почти аналогична запуску команды touch для данного файла перед запуском программы make, за исключением того, что время изменения изменяется только в представлении программы make. Смотрите раздел 9.3 [Вместо исполнения команд].
Порождает предупреждающее сообщение всегда, когда программа make встречает ссылку на неопределенную переменную. Это может быть полезным, когда вы пытаетесь отладить make-файлы, сложными способами использующие переменные.
Что такое Makefile и как начать его использовать

В жизни многих разработчиков найдётся история про первый рабочий день с новым проектом. После клонирования основного репозитория проекта наступает этап, когда приходится вводить множество команд с определёнными флагами и в заданной последовательности. Без описания команд, в большинстве случаев, невозможно понять что происходит, например:
# Bash touch ~/.bash_history ufw allow 3035/tcp || echo 'cant configure ufw' ufw allow http || echo 'cant configure ufw' docker run \ -v /root/:/root/ \ -v /etc:/etc \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /var/tmp:/var/tmp \ -v /tmp:/tmp \ -v $PWD:/app \ --network host \ -w /app \ --env-file .env \ ansible ansible-playbook ansible/development.yml -i ansible/development --limit =localhost -vv grep -qxF 'fs.inotify.max_user_watches=524288' /etc/sysctl.conf || echo fs.inotify.max_user_watches =524288 | tee -a /etc/sysctl.conf || echo 'cant set max_user_watches' && sysctl -p sudo systemctl daemon-reload && sudo systemctl restart docker
Эти команды являются лишь частью того, что необходимо выполнить при разворачивании проекта. В приведённом примере видно, что команды сами по себе длинные, содержат много флагов, а значит, их трудно не только запомнить, но и вводить вручную. Постоянно вести документацию становится сложнее с ростом проекта, она неизбежно устаревает, а порог входа для новичков становится выше, ведь уже никто не в состоянии вспомнить всех деталей проекта. Некоторые такие команды необходимо использовать каждый день, и даже не один раз в день.
Со временем становится понятно, что нужен инструмент, способный объединить в себе подобные команды, предоставить к ним удобные шорткаты ( более короткие и простые команды) и обеспечить самодокументацию проекта. Именно таким инструментом стал Makefile и утилита make . Этот гайд расскажет, как использование этих инструментов позволит свести процесс разворачивания проекта к нескольким коротким и понятным командам:
# Bash make setup make start make test
Что такое make и Makefile
Makefile — это файл, который хранится вместе с кодом в репозитории. Его обычно помещают в корень проекта. Он выступает и как документация, и как исполняемый код. Мейкфайл скрывает за собой детали реализации и раскладывает "по полочкам" команды, а утилита make запускает их из того мейкфайла, который находится в текущей директории.
Изначально make предназначалась для автоматизации сборки исполняемых программ и библиотек из исходного кода. Она поставлялась по умолчанию в большинство *nix дистрибутивов, что и привело к её широкому распространению и повсеместному использованию. Позже оказалось что данный инструмент удобно использовать и при разработке любых других проектов, потому что процесс в большинстве своём сводится к тем же задачам — автоматизация и сборка приложений.
Применение мейка в проектах стало стандартом для многих разработчиков, включая крупные проекты. Примеры мейкфайла можно найти у таких проектов, как Kubernetes, Babel, Ansible и, конечно же, повсеместно на Хекслете.
Синтаксис Makefile
make запускает цели из Makefile, которые состоят из команд:
# Makefile цель1: # имя цели, поддерживается kebab-case и snake_case команда1 # для отступа используется табуляция, это важная деталь команда2 # команды будут выполняться последовательно и только в случае успеха предыдущей
Но недостаточно просто начать использовать мейкфайл в проекте. Чтобы получить эффект от его внедрения, понадобится поработать над разделением команд на цели, а целям дать семантически подходящие имена. Поначалу, перенос команд в Makefile может привести к свалке всех команд в одну цель с «размытым» названием:
# Makefile up: # разворачивание и запуск cp -n .env.example .env touch database/database.sqlite composer install npm install php artisan key:generate php artisan migrate --seed heroku local -f Procfile.dev # запуск проекта
Здесь происходит сразу несколько действий: создание файла с переменными окружения, подготовка базы данных, генерация ключей, установка зависимостей и запуск проекта. Это невозможно понять из комментариев и названия цели, поэтому будет правильно разделить эти независимые команды на разные цели:
# Makefile env-prepare: # создать .env-файл для секретов cp -n .env.example .env sqlite-prepare: # подготовить локальную БД touch database/database.sqlite install: # установить зависимости composer install npm install key: # сгенерировать ключи php artisan key:generate db-prepare: # загрузить данные в БД php artisan migrate --seed start: # запустить приложение heroku local -f Procfile.dev
Теперь, когда команды разбиты на цели, можно отдельно установить зависимости командой make install или запустить приложение через make start . Но остальные цели нужны только при первом разворачивании проекта и выполнять их нужно в определённой последовательности. Говоря языком мейкфайла, цель имеет пререквизиты:
# Makefile цель1: цель2 # такой синтаксис указывает на зависимость задач — цель1 зависит от цель2 команда2 # команда2 выполнится только в случае успеха команды из цель2 цель2: команда1
Задачи будут выполняться только в указанной последовательности и только в случае успеха предыдущей задачи. Значит, можно добавить цель setup , чтобы объединить в себе все необходимые действия:
# Makefile setup: env-prepare sqlite-prepare install key db-prepare # можно ссылаться на цели, описанные ниже env-prepare: cp -n .env.example .env sqlite-prepare: touch database/database.sqlite install: composer install npm install key: php artisan key:generate db-prepare: php artisan migrate --seed start: heroku local -f Procfile.dev
Теперь развернуть и запустить проект достаточно двумя командами:
# Bash make setup # выполнит последовательно: env-prepare sqlite-prepare install key db-prepare make start
Благодаря проделанной работе Makefile, команды проекта вместе с флагами сведены в Makefile. Он обеспечивает правильный порядок выполнения и не важно, какие при этом задействованы языки и технологии.
Продвинутое использование
Фальшивая цель
Использование make в проекте однажды может привести к появлению ошибки make: is up to date. , хотя всё написано правильно. Зачастую, её появление связано с наличием каталога или файла, совпадающего с именем цели. Например:
# Makefile test: # цель в мейкфайле php artisan test
# Bash $ ls Makefile test # в файловой системе находится каталог с именем, как у цели в мейкфайле $ make test # попытка запустить тесты make: `test` is up to date.
Как уже говорилось ранее, изначально make предназначалась для сборок из исходного кода. Поэтому она ищет каталог или файл с указанным именем, и пытается собрать из него проект. Чтобы изменить это поведение, необходимо в конце мейкфайла добавить .PHONY указатель на цель:
# Makefile test: php artisan test .PHONY: test
# Bash $ make test ✓ All tests passed !
Последовательный запуск команд и игнорирование ошибок
Запуск команд можно производить по одной: make setup , make start , make test или указывать цепочкой через пробел: make setup start test . Последний способ работает как зависимость между задачами, но без описания её в мейкфайле. Сложности могут возникнуть, если одна из команд возвращает ошибку, которую нужно игнорировать. В примерах ранее такой командой было создание .env-файла при разворачивании проекта:
# Makefile env-prepare: cp -n .env.example .env # если файл уже создан, то повторный запуск этой команды вернёт ошибку
Самый простой ( но не единственный) способ «заглушить» ошибку — это сделать логическое ИЛИ прямо в мейкфайле:
# Makefile env-prepare: cp -n .env.example .env || true # теперь любой исход выполнения команды будет считаться успешным
Добавлять такие хаки стоит с осторожностью, чтобы не «выстрелить себе в ногу» в более сложных случаях.
Переменные
Зачастую в команды подставляют параметры для конфигурации, указания путей, переменные окружения и make тоже позволяет этим управлять. Переменные можно прописать прямо в команде внутри мейкфайла и передавать их при вызове:
# Makefile say: echo "Hello, $(HELLO)!"
# Bash $ make say HELLO=World echo "Hello, World!" Hello, World ! $ make say HELLO=Kitty echo "Hello, Kitty!" Hello, Kitty !
Переменные могут быть необязательными и содержать значение по умолчанию. Обычно их объявляют в начале мейкфайла.
# Makefile HELLO ?=World # знак вопроса указывает, что переменная опциональна. Значение после присвоения можно не указывать. say: echo "Hello, $(HELLO)!"
# Bash $ make say echo "Hello, World!" Hello, World ! $ make say HELLO=Kitty echo "Hello, Kitty!" Hello, Kitty !
Некоторые переменные в Makefile имеют названия отличные от системных. Например, $PWD называется $CURDIR в мейкфайле:
# Makefile project-env-generate: docker run --rm -e RUNNER_PLAYBOOK =ansible/development.yml \ -v $(CURDIR)/ansible/development :/runner/inventory \ # $(CURDIR) - то же самое, что $PWD в терминале -v $(CURDIR) :/runner/project \ ansible/ansible-runner
Заключение
В рамках данного гайда было рассказано об основных возможностях Makefile и утилиты make . Более плотное знакомство с данным инструментом откроет множество других его полезных возможностей: условия, циклы, подключение файлов. В компаниях, где имеется множество проектов, написанных разными командами в разное время, мейкфайл станет отличным подспорьем в стандартизации типовых команд: setup start test deploy . .
Возможность описывать в мейкфале последовательно многострочные команды позволяет использовать его как «универсальный клей» между менеджерами языков и другими утилитами. Широкая распространённость этого инструмента и общая простота позволяют внедрить его в свой проект достаточно легко, без необходимости доработок. Но мейкфайл может быть по-настоящему большим и сложным, это можно увидеть на примере реальных проектов:
Дополнительные материалы
- Утилита make: полезный универсальный инструмент программиста — видео-версия данного гайда.
Мейкфайлы, использованные при составлении гайда: