Сегодня мы поговорим о старых-добрых Makefile‘ах и о том, как они могут облегчить современным web-разработчикам жизнь.
Начну, пожалуй, с вопросов. Бывало ли у вас при переключениях между разными проектами состояние, когда вы не знаете или не помните, какой командой нужно установить или обновить зависимости, собрать приложение или же запустить его? Вы всегда помните менеджер пакетов, который используется в этом проекте, и набор его команд и опций? Хотели бы вы, чтоб был такой унифицированный простой интерфейс для выполнения стандартного набора команд при работе с вашим приложением?
Ещё с древних (в масштабах истории современных компьютерных технологий) времён существует утилита make. Она была создана в 1977 году Стюартом Фельдманом в исследовательском центре в области телекоммуникаций «Лаборатории Белла». Её основное предназначение — автоматизация преобразования одного формата файлов в другой. Обычно это компиляция исходных файлов в объектные с последующей компоновкой в исполняющиеся файлы или библиотеки.
В ходе своей работы утилита получает информацию из текстового файла с определённым форматом (обычно их называют Makefile), в которых указывают зависимость одних файлов от других во время компиляции.
Но помимо файлов исходников в make-файле можно указывать и задачи, которые мы хотим выполнить, и их зависимости друг от друга. В терминах утилиты make задача называется целью, зависимость — реквизитом, а то, что мы хотим непосредственно выполнять в этой задаче — командой. А совокупность этих трёх понятий образует правило. Схематично одно правило можно описать так:
цель1 цель2 ...: реквизит1 реквизит2 ...
команда1
команда2
....
Важно знать! В качестве отступа при перечислении команд должен быть символ табуляции (хоть здесь не будет извечного холивара «Табуляция или пробелы?»)
Таким образом, при помощи утилиты make можно сделать удобный интерфейс для разработчика по работе с приложением, который будет полностью абстрагирован от тех конкретных инструментов, которые используются внутри.
Предположим, у меня есть два проекта. Первый из них на Java, второй — графический web-интерфейс на JS. А ещё предположим, что мне необходимо установить зависимости для каждого из них, собрать всё это и запустить. Не имея в своем арсенале make-файлов, мне пришлось бы помнить, что в первом проекте у меня для управления зависимостями и сборки используется, например, Maven, а во втором Yarn, помнить синтаксис и содержание команд, которыми нужно установить зависимости, собрать проект и запустить приложение, а также нужно не забыть все опции и их значения. Согласитесь, звучит (да и выглядит на деле) не очень. Просто к этому все уже привыкли!
Другое дело — с утилитой make. Тут я могу зайти в любой из этих проектов и ввести в консоли следующие команды:
$ make install
$ make build
$ make start
И всё! Моя задача будет выполнена в обоих проектах — зависимости установятся, проект соберётся и приложение стартанёт. Все команды по работе с конкретным инструментом зашиты в виде команд в соответствующей цели в Makefile’е. Вам не нужно это помнить, вы пишите их один раз с нужными опциями при создании make-файла и спокойно забываете. Вам также надо будет меньше стучать по своей клавиатуре, потому что команды получаются обычно гораздо короче, лаконичнее и декларативнее (make build == "сделай сборку"
— ну куда уж декларативнее!).
Если вам уже хочется больше деталей, то всю информацию по работе утилиты make можно найти в официальной документации, а общее описание на Wiki.
Phony-цели
В нашем случае под целью будем считать то действие, которое мы хотим выполнить (установка зависимостей, сборка, запуск приложения и т.д.). Как мы помним, в начале статьи я писал, что в качестве целей согласно основному назначению (трансформация содержимого файла из одного формата в другой) указывают название файла, который генерируется в результате выполнения команд. Поэтому утилиты make смотрит на содержимое текущей папки, на файлы и директории.
Поясню. Предположим, у нас есть проект, в корне которого есть папка с именем build
и Makefile’ом с содержимым:
build:
echo "Hello, World!"
А теперь если запустить make build
мы получим сообщение:
make: `build' is up to date.
И наша цель не выполнится. Дело в том, что утилита make смотрит на папку build
. Мы должны явно сказать, что у нас есть phony (с англ. фальшивые, липовые) цели, которые она будет исполнять сразу же, не обращая внимания на содержимое директории. Если мы добавим в make-файл строку со специальной целью .PHONY
:
.PHONY: build
build:
echo "Hello, World!"
То получим на выходе то, что и хотим:
echo "Hello, World!"
Hello, World!
Специальную цель .PHONY
можно объявлять и в конце файла.
Проблемы с phony-целью не было бы, если б не было пересечения с именем директории. Чтобы не зависеть от состояния текущей директории, лучше сразу прописывать через пробел все те цели, которые мы хотим исполнять при помощи make-утилиты, в список .PHONY
. В таком случае нам не придётся ничего менять при изменении содержимого директории.
А ещё с помощью утилиты make можно последовательно запускать несколько целей:
make install build start
Пример Makefile
Приведу пример простого Makefile’а для JavaScript-проекта. Внутри Makefile’a будем дёргать нужные yarn-скрипты.
.PHONY: install start build
install:
yarn
start:
yarn start
build:
yarn build
А в скриптах package.json
мы будем делать следующие вызовы:
...
"scripts": {
"start": "NODE_ENV=development webpack --watch",
"build": "NODE_ENV=production webpack -p"
},
...
В вызове webpack при start’е мы включаем режим разработки, а при сборке активируем production-режим.
Эпилог
Согласитесь, выглядит очень заманчиво! Поэтому мой вам совет — скорее берите на вооружение этот безусловно удобный инструмент, и ваш процесс разработки начнёт приносить вам ещё больше удовольствия! Начать пользоваться им — очень просто. Создайте Makefile, сделайте в нём нужные вам цели и в качестве команд введите то, что вы обычно для этого действия вводите в консоль. Главное — видеть недостатки своего рабочего процесса и желание эти недостатки исправить.
На этом всё! Надеюсь, вам понравилось. До новых статей!