Наверняка многие слышали, а возможно даже и пользовались командой make в Linux. Чаще всего это выглядит как такая последовательность команд:
make make install make clean
Как правило это нужно для сборки из исходников программ написанных на Си или Си++. Однако, кто сказал, что с помощью Makefile (так называется конфигурационный файл для команды make) нельзя собирать билды для PHP проектов? А никто не говорил, потому что можно!
Только представьте, что можно установить PHP-проект на чистый сервер с помощью одной команды:
make install
И через минуту ваше приложение будет полностью готово к работе! Вам этого мало? А что насчёт такого же удобного обновления одной командой:
make update
Основы make и Makefile
Утилита make по-умолчанию установлена в большинство современных Linux-дистрибутивов, поэтому проблем с её использованием не возникнет. И чтобы начать её использовать нужно создать файл с именем Makefile, в котором описать последовательности bash-команд. Его синтаксис банален до невозможности:
Утилита make: полезный универсальный инструмент программиста
target: [dependency [dependency [dependency . [dependency]]]] commands commands # commands
Где target собственно и есть требуемое действие, например, install или update. А dependency — зависимость, которая должна существовать в виде файла или директории на диске, либо успешное выполнение другого target. На следующих строках начиная с символа табуляции (пробелы не подойдут) можно перечислить команды, которые будут выполнены при вызове цели. Цель может не содержать команд, но при этом содержать зависимости.
Сначала проверяются и выполняются все зависимости по порядку, в случае завершения какой-либо команды из зависимости с ненулевой ошибкой, выполнение команды прерывается. Далее выполняются все команды перечисленные в самой цели, в случае завершения какой-либо команды из зависимости с ненулевой ошибкой, выполнение команды прерывается.
Можно закоментировать команду поставив перед ней знак решётки — #.
Все команды выполняются в контексте текущей директории. Если в одной из строк сделать cd some/directory, то на следующей строке директория вернётся к изначальной, поэтому часть можно увидеть в Makefile подобное содержимое:
cd $(build) something cd $(build) another cd $(build) something
Если вызвать команду make без указания target, то будет запущен самый первый найденный target в файле.
В команду make можно передавать аргументы, которые следуют после target в формате arg=value another=value. При этом в Makefile можно указать значения по-умолчанию для аргументов, а также описать константы, которые не могут быть перезаписаны пользователем . Регистр имени аргумента важен: arg и ARG будут сохранены в две независимые переменные! Также можно объявить многострочный аргумент:
define parameters parameters: database_host: mariadb database_port: 3306 endef
И при желании переопределить его при выполнении команды: make parameters=new. Внутри команд переменные можно подставлять таким образом:
Makefile. Компиляция нескольких файлов с исходным кодом
ls $ ls $(argumen)
Также можно делать условия как в контексте target, так и до target, в этом случае в условия можно определять новые target. Обратите внимание, строки не должны быть в кавычках, иначе условие не сработает! Выглядит это примерно так:
ifeq ($(test), test) echo ‘this is test’ else ifeq ($, stable) echo ‘this is stable’ else echo ‘this is unknown’ endif
Более сложный пример:
ifeq ($(test), test) check: echo ‘this is test’ else ifeq ($, stable) check^ echo ‘this is stable’ else check: echo ‘this is unknown’ endif tests: check echo $(test)
Собственно всех этих знаний должно хватить для написания достаточно сложных сценариев.
Пример Makefile для сборки PHP приложения в Docker
Подобный скрипт был написан для одного из проектов. Понятное дело, что некоторые вещи можно было сделать лучше и оптимальнее, но и в таком виде он решает все поставленные задачи и проходит все критерии приёмки.
build = beta EXECFLAGS = -u app ENVFLAGS = —env SYMFONY_ENV=prod —env SYMFONY_DEBUG=0 BUILDAPP = $(build)/app ifeq ($, beta) ports: cd $(build) sed -i -e «s/- 700:80/- 701:80/» -e «s/- 710:80/- 711:80/» docker-compose.yml else ifeq ($, stable) ports: cd $(build) sed -i -e «s/- 700:80/- 700:80/» -e «s/- 710:80/- 710:80/» docker-compose.yml else ports: cd $(build) sed -i -e «s/- 700:80/- 702:80/» -e «s/- 710:80/- 712:80/» docker-compose.yml endif define parameters parameters: database_host: mariadb database_port: 3306 endef export parameters swagger-codegen: wget http://central.maven.org/maven2/io/swagger/swagger-codegen-cli/2.3.1/swagger-codegen-cli-2.3.1.jar clone: git clone -v —progress —branch $(build) $(repository) $(build) cd $(build) git checkout $(build) allow-butbucket: cd $(build) docker-compose exec $(EXECFLAGS) php bash -c ‘ssh-keyscan -H bitbucket.org >> ~/.ssh/known_hosts’ configure: ports cd $(build) sed -i -e «s/hostname: php/hostname: $(build)-php/» -e «s/hostname: nginx/hostname: $(build)-nginx/» -e «s/hostname: mariadb/hostname: $(build)-mariadb/» -e «s/hostname: redis/hostname: $(build)-redis/» -e «s/hostname: swaggerui/hostname: $(build)-swaggerui/» docker-compose.yml up: clone configure cd $(build) docker-compose up —build -d chmod: chown 1000:1000 -R $(build)/app chmod 755 -R $(build)/app install: up chmod allow-butbucket chmod 777 /tmp/composer/ chown 1000:1000 -R $(build)/storage/php/var/www/app/current/var/ cd $(build) docker-compose exec $(EXECFLAGS) php mkdir -p var/tmp cd $(build) docker-compose exec $(EXECFLAGS) php bash -c «echo ‘$$parameters’ > app/config/parameters.yml» cd $(build) docker-compose exec $(EXECFLAGS) $(ENVFLAGS) php composer install —no-dev cd $(build) docker-compose exec $(EXECFLAGS) php bin/console doctrine:schema:create —env=prod # cd $(build) docker-compose exec $(EXECFLAGS) php bin/console cache:clear —env=prod —no-debug reload-fpm: cd $(build) docker-compose exec php kill -USR2 1 reload-nginx: cd $(build) docker-compose exec nginx nginx -s reload restart-nginx: cd $(build) docker-compose exec nginx nginx -s restart docker-rebuild: cd $(build) docker-compose up —build -d docker-recreate: cd $(build) docker-compose up —force-recreate -d $(services) reset: cd $(build) git fetch cd $(build) git reset —hard origin/$(build) composer-install: cd $(build) docker-compose exec $(EXECFLAGS) $(ENVFLAGS) php composer install —no-dev schema-update: cd $(build) docker-compose exec $(EXECFLAGS) $(ENVFLAGS) php bin/console doctrine:schema:update —force update: reset configure chmod docker-rebuild allow-butbucket hotfix composer-install schema-update reload-fpm down: cd $(build) docker-compose down uninstall: down rm -rf $(build) cron: cd $(build) docker-compose exec -T $(EXECFLAGS) $(ENVFLAGS) php bin/console somecommand
Часто возникающие ошибки с make и Makefile
make: *** No rule to make target `target’. Stop.
Скорее всего target с таким именем не существует в Makefile.
make: `target’ is up to date.
Этот target не будет выполнен, так как уже существует файл или директория с тем же именем что и target.
make: Nothing to be done for `target’.
Такой target не существует, однако существует файл или директория с таким именем.
make: *** [target] Error 127
Какая-то из команд в target завершилась с ошибкой 127.
Makefile:34: *** missing separator (did you mean TAB instead of 8 spaces?). Stop.
Проверьте, у вас где-то пробелы вместо табов перед описанием bash-команд.
Makefile:22: *** missing separator. Stop.
В Makefile объявлены bash-команды без отступов.
Makefile:27: *** recipe commences before first target. Stop.
В Makefile объявлены команды раньше, чем объявлен первый target.
make: Circular tests
Обнаружены циклически зависимости которые были пропущены.
make: *** No rule to make target `target’, needed by `target’. Stop.
Выполняемый target зависит от другого target, который не объявлен в Makefile.
Post Views: 3 393
Основы Docker И ещё одна интересная выжимка фактов о докере, которая поможет в кратчайшие сроки начать его продуктивное использование. Цель данной статьи…
Установить Composer как системную команду в Linux Сейчас мало проектов на PHP обходятся без пакетного менеджера Composer. Однако, он не является предустановленным пакетом в операционных системах даже…
Ротация логов docker контейнеров В продолжение прошлой статьи рассмотрим пример настройки ротации логов контейнеров на примере CentOs 7. В моём случае stdout и stderr контейнеров…
Источник: evilinside.ru
What is a Makefile? | Turning Git’s Source Code into a Program
In this article, we’ll discuss how Make and Makefiles work at a high level, then as an example describe how Git’s original Makefile works and walk through it line by line.
Turning Source Code into a Program
Before getting straight into Makefiles, lets briefly cover how source code gets turned into an actual program that can run on a computer. Source code consists of a set of files and folders that contain code. Make is often used for C or C++ programs being compiled on Linux systems. Therefore, it is common to see C source files and cpp source files in projects that use makefiles.
This source code usually needs to be converted into a form that the computer can understand. This process is called compilation or compiling. A program that performs this conversion is called a compiler.
Sometimes the compiler needs to be given certain pieces of information so it can properly do its job. This information may include:
- The names and locations of the source code (input) files to compile
- The set of compiled (output) programs to create
- The names and locations to put the compiled (output) programs
- Whether or not to apply any special options in the compilation process
The process of choosing a compiler, identifying the set of source code files to be included, performing preperation steps, and compiling the code into its final form is called building, or the build process.
What is Make?
Make is a build automation tool. It would be very tedious for a developer to manually run all of the build steps in sequence each time they want to build their program. Build automation tools like Make allow developers to describe the build steps and execute them all at once.
What is a Makefile?
Makefiles are text files that developers use to describe the build process for their programs. The make command can then be used to conveniently run the instructions in the Makefile.
Makefile Structure: Targets, Prerequisites (Dependencies), and Recipes
The basic structure of a makefile is as follows:
target1: prerequisite1 recipe1 target2: prerequisite2 recipe2 .
Makefiles contain a list of named sets of commands that can be used to perform different actions within your codebase. Each named set of commands is called a makefile rule, and is made up of a makefile target, one or more optional makefile prerequisites (or makefile dependencies), and a makefile recipe.
- A makefile target is a name used to reference the corresponding set of makefile commands to be executed. It often represents a compiled output file or executable.
- A makefile prerequisite or makefile dependency is a source file or another target that is required by the current target.
- A makefile recipe is the set of commands that are executed by a specific target.
- A makefile rule is the combination of target, prerequisite/dependency, and recipe.
A target can either be a file or simply the name for a recipe of commands. When the target acts purely as a name for a set of commands, it is called a phony target. You can think of this kind of like a function name.
Executing a Makefile Target
The default makefile target is the first target listed in the makefile. In the example above this is target1 . This can be executed by simply running make in the same directory as your makefile.
You can change the default target in your makefile by adding .DEFAULT_GOAL := target2 at the beginning of the file. Now when you run make in your working directory, the target2 target will execute instead of target1 .
You can explicitly run a specific target at any time by running make . So you could run make target2 to run that target even if it isn’t the default.
Oftentimes the first (and default) target in a makefile is the all target, which calls other needed targets in the makefile in sequence. This is convenient since the developer can just run make in the working directory and the full build process will be completed without running multiple commands.
Another convention is to define a target called clean which deletes all of the compiled output files and executables from the last build. This allows the command make clean to prepare the local filesystem for subsequent builds.
Example Makefile: Git’s Original Makefile
Below is the original Makefile for Git. It is used to invoke the gcc C compiler to build binary executable files for each of the original 7 git commands:
- init-db
- update-cache
- cat-file
- show-diff
- write-tree
- read-tree
- commit-tree
This Makefile can be invoked in 3 variations (referred to as 3 targets), by running the 3 following commands from the command line shell inside the same directory as the Makefile:
- make clean: This removes all previously built executables and build files from the working directory.
- make backup: This first runs make clean and then backs up the current directory into a tar archive.
- make: This builds the codebase and creates the 7 git executables.
Enough talk — here is the code from Git’s first Makefile:
CFLAGS=-g # The `-g` compiler flag tells gcc to add debug symbols to the executable for use with a debugger. CC=gcc # Use the `gcc` C compiler.
# Specify the names of all executables to make. PROG=update-cache show-diff init-db write-tree read-tree commit-tree cat-file all: $(PROG)
install: $(PROG) install $(PROG) $(HOME)/bin/
# Include the following dependencies in the build. LIBS= -lssl
# Specify which compiled output (.o files) to use for each executable. init-db: init-db.o
update-cache: update-cache.o read-cache.o $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
show-diff: show-diff.o read-cache.o $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
write-tree: write-tree.o read-cache.o $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
read-tree: read-tree.o read-cache.o $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
commit-tree: commit-tree.o read-cache.o $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
cat-file: cat-file.o read-cache.o $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
# Specify which C header files to include in compilation/linking. read-cache.o: cache.h show-diff.o: cache.h
# Define the steps to run during the `make clean` command. clean: rm -f *.o $(PROG) temp_git_file_* # Remove these files from the current directory.
# Define the steps to run during the `make backup` command. backup: clean cd .. ; tar czvf babygit.tar.gz baby-git # Backup the current directory into a tar archive.
As seen in the example above, you can add makefile comments by starting the line with a # sign.
Makefile Variables — Advanced Make Syntax
Variables can be defined in the Makefile to hold specific values. In the Makefile above, words such as CFLAGS and CC are not special in any way. They are just variable names used to store the values that come after the equals sign. Variable names like $(CFLAGS) can be used later in the Makefile to substitute in the variable values where needed. This is convenient since we can use a variable name in multiple places, while only updating it in one place if the value changes.
Specifying the Compiler in Makefiles
Git is written in C, so this Makefile is tailored to a C build process using a C-specific pattern.
The first line CFLAGS=-g specifies the compiler flags — special compiler options — to use during compilation. In this case, the -g flag tells the compiler to output debug information to the console.
The second line CC=gcc identifies the actual compiler to use. GCC is the GNU Compiler Collection. It supports compilation of code in several programming languages including C, C++, Java, and more.
Specifying the Executables in Makefiles
The third line defines a build variable called PROG which contains the names of the executables we’ll be creating.
Linking External Libraries in Makefiles
We’ll quickly skip ahead to the line which defines the LIBS variable. This stores the external libraries that we want to link into the build process. In this case, we link in the SSL library which allows Git to access cryptographic functions like hashing.
Make Targets and Commands
Throughout the Makefile, there are multiple lines that start with a keyword followed by a colon such as all: , install: , init-db: , etc. As we mentioned earlier, each of these is called a target. Each target essentially maps to a command that you can specify when running Make, in the form make target .
For example, if you open a terminal window and browse to this Makefile’s directory, you could run the make all command to run Make on the all target. Similarly you could run make install to run Make on the install target. If no target is specified, the all target will be used by default.
When Make runs a target, it executes the instructions associated with that target in the Makefile.
The All Target
Back to the Makefile, the all: $(PROG) line states that, when Make is run without specifying a target, all targets listed in $(PROG) will be executed. Since $(PROG) lists all 7 of the Baby Git executables, each of them will be executed.
The Install Target
The next target in the Makefile is install . It is run at the command line using make install . This starts the same way as the all target, by specifying the executables to compile using $(PROG) . But then it uses the install command to move those built executables into the users home directory.
Baby Git Program Targets
Now for the targets corresponding to the executable names:
- init-db:
- update-cache:
- show-diff:
- write-tree:
- read-tree:
- commit-tree:
- cat-file:
Each one of these targets specifies which compiled C object (.o) files we want in each of our executables. Below that each one specifies the compiler command to run based no the build variables specified earlier in the file.
The first executable init-db is very simple since it only includes 1 source file: init-db: init-db.o
The other executables (we’ll take update-cache as an example) link together multiple C object (.o) files:
update-cache: update-cache.o read-cache.o $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
The second line above gets converted to the following after variable substitution:
gcc -g -o update-cache update-cache.o read-cache.o -lssl
Linking Header Files in Makefiles
After the program targets, there are two lines that specify the C header (.h) files to link to each object (.o) file. The only header file in the Baby Git codebase is cache.h , which gets linked to read-cache.o and show-diff.o . C header files typically contain function definitions and function declarations that will be included in multiple files in the codebase.
The clean Target
This target is invoked using make clean and simply deletes all compiled code and executables from the working directory. It leaves the source files alone so that the program can be built again.
The backup Target
This target is invoked using make backup . First it invokes the clean target. Then it backs up the source code files in the working directory as a tar archive in the parent directory.
GNU Make Manual
A valuable resource when working with make is the GNU Make Manual. It contains some of the official make overviews and documentation of make features and make functionality that we couldn’t cover in this article.
Summary
In this article we discussed the basic concepts, terminology and structure of makefiles. We learned about an example of how Git’s first Makefile works line by line. We hope it helped you understand how Makefiles work and how they are implemented in practice.
Next Steps
If you’re interested in learning more about how Git works under the hood, check out our Baby Git Guidebook for Developers, which dives into Git’s code in an accessible way. We wrote it for curious developers to learn how Git works at the code level. To do this we documented the first version of Git’s code and discuss it in detail.
References
- GNU Make Manual — https://www.gnu.org/software/make/manual/make.html
Источник: initialcommit.com
Автоматизируем работу с контейнерами через Makefile: сборка, тестирование и развёртывание за один вызов make
Утилита make позволяет просто управлять контейнерами, объединив команды для сборки, тестирования и развёртывания в одном конфигурационном файле.
Разработчики многие годы используют утилиту make. При запуске утилита читает файл с описанием проекта (Makefile) и, интерпретируя его содержимое, предпринимает необходимые действия. Файл с описанием проекта представляет собой текстовый конфигурационный файл, где описаны зависимости и команды, которые необходимо выполнить. Он похож на Dockerfile или другой файл конфигурации контейнера: там тоже указаны команды, на основе которых формируются образы для развёртывания контейнеров.
В этой статье я расскажу о том, как управлять контейнерами, используя Makefile. Контейнерный конфигурационный файл описывает образ контейнера, а Makefile описывает процесс сборки проекта, тестирование и развёртывание, а также другие полезные команды.
Цели и структура Makefile
Утилита make по умолчанию установлена в большинстве современных Linux-дистрибутивов, поэтому проблем с её использованием обычно не возникает. И чтобы начать её использовать, нужно создать файл с именем Makefile.
Makefile состоит из набора целей (target), зависимостей (dependency) и команд (commands), необходимых для их выполнения:
target: [dependency [dependency [dependency . [dependency]]]] commands commands # commands
Цель — это некий желаемый результат, способ достижения которого описан в Makefile. Под целью подразумевают выполнение некого действия либо получение новой версии файла. А dependency — это некие «исходные данные», условия необходимые для достижения указанной цели. Зависимость может быть результатом выполнения другой цели, либо обычным файлом.
Команды вводят с использованием символа табуляции (пробелы не подойдут). Цель может не содержать команд, но при этом содержать зависимости.
Сначала проверяются и выполняются все зависимости по порядку, в случае завершения какой-либо команды из зависимости с ненулевой ошибкой, выполнение команды прерывается. Далее выполняются все команды перечисленные в самой цели, в случае завершения какой-либо команды с ненулевой ошибкой, выполнение цели прерывается.
Все команды выполняются в контексте текущей директории. Можно закомментировать команду поставив перед ней знак решётки (#).
Чтобы отправить цель на выполнение, нужно указать её название при вызове утилиты make:
# Выполнение цели «build_image» $ make build_image
В этом вся прелесть Makefile. Вы можете создать набор целей для каждой задачи. В контексте управления контейнерами, это создание образа и его отправка в реестр, тестирование и развёртывание контейнера, а также обновление его сервиса.
Я проиллюстрирую использование Makefile на примере своего личного веб-сайта и покажу как просто можно автоматизировать выполнение перечисленных задач.
Сборка, тестирование и развёртывание
Я создал простой веб-сайт с помощью Hugo, генератора статических сайтов. Он позволяет получить статический HTML из файлов YAML. В качестве веб-сервера я использовал Caddy.
Теперь посмотрим, как Makefile упростит сборку и развёртывание этого проекта на проде.
Первой целью в Makefile будет image_build:
image_build: podman build —format docker -f Containerfile -t $(IMAGE_REF):$(HASH) .
Эта цель вызывает Podman для создания контейнера из его конфигурационного файла (Containerfile), включенного в проект. В приведённой выше команде есть несколько переменных. Переменные в Makefile можно использовать примерно так же, как в простых скриптовых языках программирования. В данном случае мне это нужно для создания «ссылки» на образ, который будет отправлен в удалённый реестр:
# Image values REGISTRY := «us.gcr.io» PROJECT := «my-project-name» IMAGE := «some-image-name» IMAGE_REF := $(REGISTRY)/$(PROJECT)/$(IMAGE) # Git commit hash HASH := $(shell git rev-parse —short HEAD)
Используя эти переменные, цель image_build формирует идентификатор вида us.gcr.io/my-project-name/my-image-name:abc1234.
В конце, в качестве тега образа добавлен хэш соответствующей Git-ревизии.
В нашем случае образ будет помечен тегом :latest. Этот тег нам пригодится для очистки контейнера:
image_tag: podman tag $(IMAGE_REF):$(HASH) $(IMAGE_REF):latest
Итак, теперь контейнер создан, и его необходимо проверить, чтобы убедиться, что он соответствует некоторым минимальным требованиям. Для моего личного веб-сайта важно ответить на два вопроса:
- «Запускается ли веб-сервер?»
- «Что он возвращает?»
#!/usr/bin/env python3 import time import argparse from subprocess import check_call, CalledProcessError from urllib.request import urlopen, Request parser = argparse.ArgumentParser() parser.add_argument(‘-i’, ‘—image’, action=’store’, required=True, help=’image name’) args = parser.parse_args() print(args.image) try: check_call(«podman rm smk».split()) except CalledProcessError as err: pass check_call( «podman run —rm —name=smk -p 8080:8080 -d <>».format(args.image).split() ) time.sleep(5) r = Request(«http://localhost:8080», headers=) try: print(str(urlopen(r).read())) finally: check_call(«podman kill smk».split())
Проверку можно усложнить. Например, во время процесса сборки можно потребовать, чтобы в ответе содержался хэш Git-ревизии. И тогда мы сможем проверить, содержится ли ответ заданный хэш.
Если с тестами всё хорошо, образ готов к развёртыванию. Я использую для размещения своего веб-сайта сервис Google Cloud Run: скармливаю ему образ, и он мгновенно выдаёт мне URL. Как и многие подобные сервисы, с ним можно общаться через интерфейс командной строки (CLI). С Cloud Run развёртывание сводится к отправке образов (созданных локально) в удалённый реестр контейнеров и к запуску процесса самого развёртывания с помощью инструмента командной строки gcloud.
Теперь создадим цель push. Я используюPodman (но можно вместо него использовать Skopeo или тот же Docker).
push: podman push —remove-signatures $(IMAGE_REF):$(HASH) podman push —remove-signatures $(IMAGE_REF):latest
После того, как образ будет отправлен, используйте команду gcloud run deploy, чтобы развернуть новейшую версию образа в проекте и «оживить» его.
Давайте снова создадим цель в Makefile. В этом файле я могу создать переменные для аргументов —platform и —region, чтобы мне не нужно было каждый раз запоминать их. Иначе мне пришлось бы вводить их из головы каждый раз, когда я развёртываю новый образ. А я так редко пишу для своего личного блога, что нет никаких шансов, что я запомню эти переменные.
rollout: gcloud run deploy $(PROJECT) —image $(IMAGE_REF):$(HASH) —platform $(PLATFORM) —region $(REGION)
Дополнительные цели
Локальный запуск
При тестировании CSS или других изменений кода мне хочется проверять работу проекта локально, без развёртывания на удалённом сервере. Для этого в мой Makefile я добавлю цель run_local.
Она выбирает контейнер в соответствии с моим текущим коммитом и открывает в браузере URL-адрес страницы с локального веб-сервера.
В традиционных реализациях, у программы make нет надежного способа узнать, чем именно является цель. Ведь она может быть как именем действия, так и именем файла. Утилита make просто ищет на диске файл с именем, которое указано в качестве цели. Если такой файл существует, то цель считается именем файла.
Поэтому приходится явно объявлять цели абстрактными (то есть действиями). Для этого достаточно добавить ключевое слово .PHONY.
Создадим абстрактную цель run_local:
.PHONY: run_local run_local: podman stop mansmk ; podman rm mansmk ; podman run —name=mansmk —rm -p $(HOST_ADDR):$(HOST_PORT):$(TARGET_PORT) -d $(IMAGE_REF):$(HASH) $(BROWSER) $(HOST_URL):$(HOST_PORT)
Я также использую переменную для имени браузера, поэтому при желании могу тестировать на разных браузерах. Когда я запускаю make run_local, по умолчанию сайт открывается в Firefox. Чтобы протестировать то же самое в Google Chrome, я должен модифицировать вызов make run_local, добавив BROWSER = ‘google-chrome’.
Очистка старых контейнеров и образов
При работе с контейнерами очистка старых образов — неприятная рутинная работа, особенно при частых итерациях. Поэтому добавим в Makefile цели для выполнения этих задач. Если контейнер не существует, Podman или Docker вернутся из процесса очистки с кодом 125. Но к сожалению, make ожидает, что каждая команда вернёт 0 (если всё ОК) или прекратит работу (если что-то не так). Поэтому придётся написать на bash вот такую обработку:
#!/usr/bin/env bash ID=»$» podman stop $ 2>/dev/null if [[ $? == 125 ]] then # No such container exit 0 elif [[ $? == 0 ]] then podman rm $ 2>/dev/null else exit $? fi
Для очистки образов требуется реализовать более сложную логику, но всё это можно сделать в Makefile. Чтобы сделать это легко, я добавлю метку (через Containerfile) к образу (на этапе его создания). Это позволяет легко найти все образы с заданными метками. Самые последние из них можно определить по тегу :latest. И теперь все образы, кроме самых последних (с тегом: latest), могут быть удалены:
clean_images: $(eval LATEST_IMAGES := $(shell podman images —filter «label=my-project.purpose=app-image» —no-trunc | awk ‘/latest/ ‘)) podman images —filter «label=my-project.purpose=app-image» —no-trunc —quiet | grep -v $(LATEST_IMAGES) | xargs —no-run-if-empty —max-lines=1 podman image rm
Связанные одной целью
На данный момент мой Makefile включает команды для создания и маркировки образов, тестирования, отправки образов, развёртывания новых версий, очистки образов и запуска локальной версии. Выполнять каждую из них с помощью make image_build make image_tag make test и так далее значительно проще, чем выполнение каждой из команд, находящихся внутри этих вызовов. Но всё можно упростить ещё больше.
Я хочу, чтобы make в моём проекте по умолчанию делала всё — от создания образа до тестирования, развёртывания и очистки:
.PHONY: all all: build test deploy clean .PHONY: build image_build image_tag build: image_build image_tag .PHONY: deploy push rollout deploy: push rollout .PHONY: clean clean_containers clean_images clean: clean_containers clean_images
Makefile может запускать сразу несколько целей. Сгруппируем цели image_build и image_tag внутри одной цели build. Теперь, чтобы запустить их, нужно просто вызвать make build. Более того, цели build, test, deploy и clean ядополнительно сгруппирую в цель all, что позволит мне запускать их все (в указанной последовательности) за один вызов:
$ make all
$ make
Не только контейнеры
C помощью Makefile можно объединить все команды, необходимые для сборки, тестирования и развёртывания проекта. Так можно упростить и автоматизировать огромное количество задач.
Но Makefile можно использовать и для задач, связанных с разработкой: запуск модульных тестов, компиляция двоичных файлов и формирование контрольных сумм.
Жаль, Makefile не может писать код за нас (подмигивает).
Купить VDS-хостинг с быстрыми NVMе-дисками и посуточной оплатой у хостинга Маклауд.
Источник: habr.com