Наверняка многие слышали, а возможно даже и пользовались командой make в Linux. Чаще всего это выглядит как такая последовательность команд:
make && make install && make clean
Как правило это нужно для сборки из исходников программ написанных на Си или Си++. Однако, кто сказал, что с помощью Makefile (так называется конфигурационный файл для команды make) нельзя собирать билды для PHP проектов? А никто не говорил, потому что можно!
Только представьте, что можно установить PHP-проект на чистый сервер с помощью одной команды:
make install
И через минуту ваше приложение будет полностью готово к работе! Вам этого мало? А что насчёт такого же удобного обновления одной командой:
make update
Основы make и Makefile
Утилита make по-умолчанию установлена в большинство современных Linux-дистрибутивов, поэтому проблем с её использованием не возникнет. И чтобы начать её использовать нужно создать файл с именем Makefile, в котором описать последовательности bash-команд. Его синтаксис банален до невозможности:
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. Внутри команд переменные можно подставлять таким образом:
ls ${argument}
ls $(argumen)
Также можно делать условия как в контексте target, так и до target, в этом случае в условия можно определять новые target. Обратите внимание, строки не должны быть в кавычках, иначе условие не сработает! Выглядит это примерно так:
ifeq ($(test), test)
echo 'this is test'
else ifeq (${test}, stable)
echo 'this is stable'
else
echo 'this is unknown'
endif
Более сложный пример:
ifeq ($(test), test)
check:
echo 'this is test'
else ifeq (${test}, 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 (${build}, beta)
ports:
cd $(build) && sed -i -e "s/- 700:80/- 701:80/" -e "s/- 710:80/- 711:80/" docker-compose.yml
else ifeq (${build}, 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 <- tests dependency dropped.
Обнаружены циклически зависимости которые были пропущены.
make: *** No rule to make target `target’, needed by `target’. Stop.
Выполняемый target зависит от другого target, который не объявлен в Makefile.