Поиск по сайту:

Как использовать Makefile для автоматизации повторяющихся задач


Введение

Если у вас есть опыт установки программного обеспечения из исходного кода на ваш Linux-сервер, вы, вероятно, сталкивались с утилитой make. Этот инструмент в основном используется для компиляции и сборки программ. Это позволяет автору исходного кода изложить шаги, необходимые для создания этого конкретного проекта.

Хотя make был создан для автоматизации компиляции программного обеспечения, этот инструмент был разработан достаточно гибко, чтобы его можно было использовать для автоматизации почти любой задачи, которую можно выполнить из командной строки. В этом руководстве мы обсудим, как можно перепрофилировать make для автоматизации повторяющихся задач, которые выполняются последовательно.

Предпосылки

Для этого руководства подойдет любая среда Linux. Инструкции по установке пакетов предоставляются как для Ubuntu/Debian Linux, так и для Red Hat/Rocky Linux.

Установка Make

Большинство дистрибутивов Linux позволяют установить компилятор с помощью одной команды, но не предоставляют ее по умолчанию.

В Ubuntu вы можете установить пакет под названием build-essential, который предоставит все пакеты, необходимые для современной хорошо поддерживаемой среды компилятора. Обновите исходники пакетов и установите пакет с помощью apt:

  1. sudo apt update
  2. sudo apt install build-essential

В Rocky Linux или других производных от Red Hat вы можете установить группу пакетов под названием «Инструменты разработки», чтобы обеспечить ту же функциональность компилятора. Установите пакеты с помощью dnf:

  1. dnf groups mark install "Development Tools"
  2. dnf groupinstall "Development Tools"

Вы можете убедиться, что компилятор доступен, проверив наличие команды make в вашей системе. Для этого используйте команду what:

  1. which make
Output
/usr/bin/make

Теперь у вас есть инструменты, которые позволят вам использовать make в его обычном качестве.

Понимание Makefile

Основной способ, которым команда make получает инструкции, — это использование Makefile.

Makefiles зависят от каталога, а это означает, что make будет искать эти файлы в каталоге, в котором он был вызван. Мы должны поместить Makefile в корень любой задачи, которую мы собираемся выполнять, или туда, где имеет смысл вызывать скрипты, которые мы напишем.

Внутри Makefile мы следуем определенному формату. Make использует концепции целей, источников и команд следующим образом:

target: source
    command

Выравнивание и формат этого очень важны. Мы обсудим формат и значение каждого из этих компонентов здесь:

Цель

Целью является указанное пользователем имя для ссылки на группу команд. Думайте об этом как о функции в языке программирования.

Цель выравнивается по левому столбцу, представляет собой непрерывное слово (без пробелов) и заканчивается двоеточием (:).

При вызове make мы можем указать цель, набрав:

  1. make target_name

Затем Make проверит Makefile и выполнит команды, связанные с этой целью.

Источник

Источники — это ссылки на файлы или другие цели. Они представляют предварительные условия или зависимости для цели, с которой они связаны.

Например, у вас может быть раздел вашего Makefile, который выглядит так:

target1: target2
    target1_command

target2:
    target2_command

В этом примере мы могли бы вызвать target1 следующим образом:

  1. make target1

Затем Make перейдет к Makefile и найдет цель target1. Затем он проверит, указаны ли какие-либо источники.

Он найдет исходную зависимость target2 и временно перейдет к этой цели.

Оттуда он проверит, есть ли в target2 какие-либо источники в списке. Это не так, поэтому он продолжит выполнение target2_command. В этот момент make достигнет конца списка команд target2 и передаст управление обратно цели target1. Затем он выполнит target1_command и выйдет.

Источниками могут быть как файлы, так и сами цели. Make использует метки времени файла, чтобы увидеть, был ли файл изменен с момента его последнего вызова. Если в исходный файл были внесены изменения, эта цель запускается повторно. В противном случае он помечает зависимость как выполненную и переходит к следующему источнику или к командам, если это был единственный источник.

Общая идея заключается в том, что, добавляя источники, мы можем построить последовательный набор зависимостей, которые должны выполняться перед текущей целью. Вы можете указать несколько источников, разделенных пробелами, после любой цели. Вы можете начать понимать, как можно задавать сложные последовательности задач.

Команды

Что дает команде make такую гибкость, так это то, что командная часть синтаксиса очень открыта. Вы можете указать любую команду для запуска под целью. Вы можете добавить столько команд, сколько необходимо.

Команды указываются в строке после объявления цели. Они имеют отступ в один символ табуляции. В некоторых версиях make предусмотрена гибкая настройка отступа в разделе команд, но в целом следует придерживаться одной вкладки, чтобы гарантировать, что make распознает ваши намерения.

Make считает каждую строку с отступом под целевым определением отдельной командой. Вы можете добавить столько строк и команд с отступом, сколько захотите. Make будет проходить их по одному.

Есть несколько вещей, которые мы можем поместить перед командами, чтобы заставить make обрабатывать их по-другому:

  • -: тире перед командой говорит make не прерывать работу в случае возникновения ошибки. Например, это может быть полезно, если вы хотите выполнить команду над файлом, если он присутствует, и ничего не делать, если его нет.
  • @: Если вы вводите команду с символом @, сам вызов команды не будет выводиться в стандартный вывод. Это в основном используется только для очистки вывода, который производит make.

Дополнительные возможности

Некоторые дополнительные функции могут помочь вам создать более сложные цепочки правил в вашем Makefile.

Переменные

Make распознает переменные (или макросы), которые ведут себя как заполнители для подстановки в вашем make-файле. Лучше всего объявить их в начале вашего файла.

Имя каждой переменной полностью пишется с заглавной буквы. После имени знак равенства присваивает имя значению справа. Например, если мы хотим определить каталог установки как /usr/bin, мы можем добавить INSTALLDIR=/usr/bin вверху файла.

Позже в файле мы можем сослаться на это местоположение, используя синтаксис $ (INSTALLDIR).

Побег новых строк

Еще одна полезная вещь, которую мы можем сделать, — разрешить командам занимать несколько строк.

Мы можем использовать любую команду или функциональность оболочки в разделе команд. Это включает в себя экранирование символов новой строки, заканчивая строку \:

target: source
    command1 arg1 arg2 arg3 arg4 \
    arg5 arg6

Это становится более важным, если вы воспользуетесь преимуществами некоторых более программных функций оболочки, таких как операторы if-then:

target: source
    if [ "condition_1" == "condition_2" ];\
    then\
        command to execute;\
        another command;\
    else\
        alternative command;\
    fi

Это выполнит этот блок, как если бы это была однострочная команда. На самом деле, мы могли бы написать это одной строкой, но это значительно улучшает читабельность, разбивая ее таким образом.

Если вы собираетесь экранировать символы конца строки, убедитесь, что после \ нет лишних пробелов или табуляции, иначе вы получите ошибку.

Правила суффиксов файлов

Дополнительная функция, которую вы можете использовать для обработки файлов, — это файловые суффиксы. Это общие правила, которые обеспечивают способ обработки файлов на основе их расширения.

Например, если вы хотите обработать все файлы .jpg в каталоге и преобразовать их в файлы .png с помощью пакета ImageMagick, у нас может быть что-то вроде этого в нашем Makefile:

.SUFFIXES: .jpg .png

.jpg.png:
    @echo converting $< to $@
    convert $< $@

Есть несколько вещей, на которые нам нужно обратить внимание.

Первая часть — это объявление .SUFFIXES:. Это сообщает make обо всех суффиксах, которые мы будем использовать в суффиксах файлов. Некоторые суффиксы, которые часто используются при компиляции исходного кода, такие как файлы .c и .o, включаются по умолчанию, и их не нужно указывать в этом объявлении.

Следующая часть — это объявление фактического правила суффикса. Это принимает форму исходное_расширение.целевое_расширение:.

Это не фактическая цель, но она будет соответствовать любому вызову файлов со вторым расширением и создавать их из файла с первым расширением.

В нашем случае мы можем вызвать make таким образом, чтобы создать файл с именем file.png, если в нашем каталоге есть file.jpg:

  1. make file.png

make найдет файл png в объявлении .SUFFIXES и увидит правило создания файлов .png. Затем он будет искать целевой файл с заменой .png на .jpg в каталоге. Затем он выполнит следующие команды.

Суффиксные правила используют некоторые переменные, с которыми мы еще не знакомы. Это помогает заменить различную информацию в зависимости от того, в какой части процесса она находится в данный момент:

  • $?: Эта переменная содержит список зависимостей для текущей цели, которые более поздние, чем цель. Это будут цели, которые необходимо выполнить повторно перед выполнением команд под этой целью.
  • $@: Эта переменная является именем текущей цели. Это позволяет нам ссылаться на файл, который вы пытаетесь создать, даже если это правило соответствует шаблону.
  • $<: это имя текущей зависимости. В случае правил суффикса это имя файла, который используется для создания цели. В нашем примере это будет file.jpg
  • $*: этот файл является именем текущей зависимости с удаленным соответствующим расширением. Считайте это промежуточным этапом между целевым и исходным файлами.

Создайте Makefile преобразования

Мы создадим Makefile, который будет выполнять некоторые манипуляции с изображениями, а затем загружать файлы на наш файловый сервер, чтобы наш веб-сайт мог их отображать.

Если вы хотите продолжить, прежде чем начать, убедитесь, что у вас установлены пакеты ImageMagick. Это инструменты командной строки для управления изображениями, и мы будем использовать их в нашем скрипте.

В Ubuntu или Debian обновите исходники пакетов и установите с помощью apt:

  1. sudo apt-get update
  2. sudo apt-get install imagemagick

В Red Hat или Rocky вам потребуется добавить репозиторий epel-release, чтобы получить дополнительные пакеты, подобные этому, а затем установить пакет с помощью dnf:

  1. dnf install epel-release
  2. dnf install ImageMagick

В текущем каталоге создайте файл с именем Makefile:

  1. nano Makefile

В этом файле мы начнем реализовывать наши цели конверсии.

Конвертировать все файлы JPG в PNG

Наш сервер настроен исключительно для обслуживания изображений .png. Из-за этого нам нужно конвертировать любые файлы .jpg в .png перед загрузкой.

Как мы узнали выше, правило суффиксов — отличный способ сделать это. Мы начнем с объявления .SUFFIX, в котором будут перечислены форматы, между которыми мы конвертируем: .SUFFIXES: .jpg .png.

После этого мы можем создать правило, которое изменит файлы .jpg на файлы .png. Мы можем сделать это с помощью команды convert из пакета ImageMagick. Синтаксис команды convert следующий: convert from_file to_file.

Чтобы реализовать эту команду, нам нужно правило суффикса, определяющее формат, с которого мы начинаем, и формат, которым мы заканчиваем:

.SUFFIXES: .jpg .png

.jpg.png:           ## This is the suffix rule declaration

Теперь, когда у нас есть правило, которое будет соответствовать, нам нужно реализовать фактический шаг преобразования.

Поскольку мы не знаем точно, какое имя файла здесь будет соответствовать, нам нужно использовать переменные, о которых мы узнали. В частности, нам нужно указать $< как исходный файл и $@ как файл, в который мы конвертируем. Если мы объединим это с тем, что мы знаем о команде convert, мы получим следующее правило:

.SUFFIXES: .jpg .png

.jpg.png:
    convert $< $@

Давайте добавим некоторую функциональность, чтобы нам можно было явно сказать, что происходит с оператором echo. Мы включим символ @ перед новой командой и командой, которая у нас уже была, чтобы фактическая команда не печаталась при ее выполнении:

.SUFFIXES: .jpg .png

.jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

На этом этапе мы должны сохранить и закрыть файл, чтобы мы могли его протестировать.

Получить файл jpg в текущий каталог. Если у вас нет файла под рукой, вы можете загрузить его с веб-сайта DigitalOcean с помощью wget:

  1. wget https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.png
  2. mv DO_Powered_by_Badge_blue.png badge.jpg

Вы можете проверить, работает ли ваш make-файл, попросив его создать файл badge.png:

  1. make badge.png
Output
converting badge.jpg to badge.png using ImageMagick... conversion to badge.png successful!

Make перейдет к Makefile, увидит .png в объявлении .SUFFIXES, а затем перейдет к соответствующему правилу суффикса. Затем он запускает перечисленные команды.

Создать список файлов

На этом этапе make может создать файл .png, если мы явно укажем ему, что нам нужен этот файл.

Было бы лучше, если бы он просто создал список файлов .jpg в текущем каталоге, а затем преобразовал их. Мы можем сделать это, создав переменную, которая содержит все наши файлы для преобразования.

Лучше всего это сделать с помощью директивы подстановочного знака, например JPG_FILES=$ (подстановочный знак *.jpg).

Мы могли бы просто указать цель с подстановочным знаком bash, например JPG_FILES=*.jpg, но у этого есть недостаток. Если файлов .jpg нет, это фактически попытается запустить команды преобразования для файла с именем *.jpg, что завершится ошибкой.

Синтаксис подстановочных знаков, о котором мы упоминали выше, составляет список файлов .jpg в текущем каталоге, и если их нет, он ничего не устанавливает для переменной.

Пока мы делаем это, мы должны попытаться справиться с небольшими вариациями в файлах .jpg, которые распространены. Эти файлы изображений часто имеют расширение .jpeg вместо .jpg. Чтобы обрабатывать их автоматически, мы можем изменить их имя в нашей программе на файлы .jpg.

Вместо вышеуказанных строк мы будем использовать эти две:

JPEG=$(wildcard *.jpg *.jpeg)     ## Has .jpeg and .jpg files
JPG=$(JPEG:.jpeg=.jpg)            ## Only has .jpg files

Первая строка составляет список файлов .jpg и .jpeg в текущем каталоге и сохраняет их в переменной с именем JPEG.

Вторая строка ссылается на эту переменную и выполняет преобразование имени для преобразования имен в переменной JPEG, заканчивающихся на .jpeg, в имена, заканчивающиеся на .jpg. Это делается с помощью синтаксиса $ (ИМЯ ПЕРЕМЕННОЙ:.convert_from=.convert_to).

В конце этих двух строк у нас будет новая переменная с именем JPG, которая содержит только имена файлов .jpg. Некоторые из этих файлов могут фактически не существовать в системе, потому что они на самом деле являются файлами .jpeg (фактического переименования не было). Это нормально, потому что мы используем этот список только для создания нового списка файлов .png, которые мы хотим создать:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)

Теперь у нас есть список файлов, которые мы хотим запросить в переменной PNG. Этот список содержит только имена файлов .png, потому что мы сделали другое преобразование имен. Теперь каждый файл, который был файлом .jpg или .jpeg в этом каталоге, использовался для составления списка файлов .png, которые мы хотим создать.

Нам также необходимо обновить объявление .SUFFIXES и правило суффиксов, чтобы отразить, что мы теперь обрабатываем файлы .jpeg:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

Как видите, мы добавили .jpeg в список суффиксов, а также включили еще один суффикс, соответствующий нашему правилу.

Создайте несколько целей

Сейчас у нас довольно много в нашем Makefile, но у нас пока нет обычных целей. Давайте исправим это, чтобы мы могли передать наш список PNG в наше правило суффикса:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

convert: $(PNG)

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

Все, что делает эта новая цель, — это список имен файлов .png, которые мы собрали в качестве требования. Затем Make проверяет, есть ли способ получить файлы .png, и использует для этого правило суффикса.

Теперь мы можем использовать эту команду для преобразования всех наших файлов .jpg и .jpeg в файлы .png:

  1. make convert

Добавим еще одну цель. Другая задача, которая обычно выполняется при загрузке изображений на сервер, — изменение их размера. Наличие изображений правильного размера избавит ваших пользователей от необходимости изменять размер изображений на лету, когда они их запрашивают.

Команда ImageMagick под названием mogrify может изменять размер изображений так, как нам нужно. Допустим, область, в которой будут отображаться наши изображения на нашем сайте, имеет ширину 500 пикселей. Мы можем конвертировать для этой области с помощью команды:

  1. mogrify -resize 500\> file.png

Это изменит размер любых изображений шириной более 500 пикселей, чтобы они соответствовали этой области, но не коснется меньших изображений. Это то, чего мы хотим. В качестве цели мы можем добавить это правило:

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

Мы можем добавить это в наш файл следующим образом:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

Теперь мы можем связать эти две цели вместе как зависимости от другой цели:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

webify: convert resize

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

Вы можете заметить, что неявное изменение размера будет выполнять те же команды, что и преобразование. Мы собираемся указать их обоих, хотя это не всегда так. Преобразование может в будущем содержать более сложную обработку.

Цель webify теперь конвертирует изображения и изменяет их размер.

Загрузить файлы на удаленный сервер

Теперь, когда у нас есть готовые изображения для Интернета, мы можем создать цель для загрузки их в каталог статических изображений на нашем сервере. Мы можем сделать это, передав наш список преобразованных файлов в scp:

Наша цель будет выглядеть примерно так:

upload: webify
    scp $(PNG) root@ip_address:/path/to/static/images

Это загрузит все наши файлы на удаленный сервер. Наш файл теперь выглядит так:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

upload: webify
    scp $(PNG) root@ip_address:/path/to/static/images

webify: convert resize

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

Очистить

Давайте добавим опцию очистки, чтобы избавиться от всех локальных файлов .png после их загрузки на удаленный сервер:

clean:
    rm *.png

Теперь мы можем добавить еще одну цель вверху, которая вызывает эту цель после того, как мы загрузим наши файлы на удаленный сервер. Это будет самая полная цель, которую мы хотим использовать по умолчанию.

Чтобы указать это, мы поместим его в качестве первой доступной цели. Это будет использоваться по умолчанию. Мы будем называть его all по соглашению:

JPEG=$(wildcard *.jpg *.jpeg)
JPG=$(JPEG:.jpeg=.jpg)
PNG=$(JPG:.jpg=.png)
.SUFFIXES: .jpg .jpeg .png

all: upload clean

upload: webify
    scp $(PNG) root@ip_address:/path/to/static/images

webify: convert resize

convert: $(PNG)

resize: $(PNG)
    @echo resizing file...
    @mogrify -resize 648\> $(PNG)
    @echo resizing is complete!

clean:
    rm *.png

.jpeg.png .jpg.png:
    @echo converting $< to $@ using ImageMagick...
    @convert $< $@
    @echo conversion to $@ successful!

С этими последними штрихами, если вы войдете в каталог с Makefile и файлами .jpg или .jpeg, вы можете вызвать make без каких-либо аргументов для обработки ваших файлов, отправить их на ваш сервер, а затем удалить загруженные вами файлы .png.

  1. make

Как видите, можно объединять задачи, а также выбирать процесс до определенного момента. Например, если вы хотите преобразовать только свои файлы и разместить их на другом сервере, вы можете просто использовать цель webify.

Заключение

К этому моменту у вас должно быть хорошее представление о том, как использовать Makefile в целом. В частности, вы должны знать, как использовать make в качестве инструмента для автоматизации большинства видов процедур.

Хотя в некоторых случаях лучше написать сценарий, файлы Makefile — это способ установить структурированные иерархические отношения между процессами. Изучение того, как использовать этот инструмент, может помочь сделать повторяющиеся задачи более управляемыми.