How to make use of Makefile to shorthand long commands
I like automation and none repetitive tasks, also, I don’t like to write too much when it comes to handling with cli
commands. And, since I work much with docker
in my projects, either backend or frontend and specially docker-compose
, I spend extra time writing and thinking about the command I would like to run inside my container, so, let’s say I would like to run a simple composer install
so this will result in typing
# where `api` is the docker service name
$ docker-compose exec api composer install
But, by the end of this article you will be able to run in just two words, also with the support of autocomplete.
# where `api` is the docker service name
$ make composer-install
I picked up the habit of making use of make
files for my projects, when I used to work at leaseweb, that it was of a great use, and I continued using it and extended the use of it with some extra logic.
This article is not intended to be dedicated for php
and symfony
projects only, but those are used as an example only
How does it work ?
Let’s try it out together, you don’t need to install anything extra on your mac
or linux
, it should work out of the box in you bash
or zsh
terminal.
Also, let’s set a base ground for this how-to, and assume that:
- We have a symfony
php
project connected to amysql
db running onnginx
andphp-fpm
. - All of these are running in
docker
containers as services. docker
is used only in development and not in production.
So, your project’s docker-compose.yml
file looks like below
version: "3.7"
services:
db:
image: mysql:8
env_file:
- .docker.mysql.env
restart: always
volumes:
- dbdata:/var/lib/mysql
ports:
- 3307:3306
networks:
- backend
nginx:
build: docker/nginx
restart: on-failure
ports:
- "8080:80"
volumes:
- ./:/var/www/api:delegated
- ./var/logs/nginx/:/var/log/nginx:delegated
- ./docker/nginx/api.conf:/etc/nginx/conf.d/default.conf:delegated
depends_on:
- db
- api
networks:
- backend
api:
build: docker/php
restart: on-failure
depends_on:
- db
volumes:
- ./:/var/www/api:delegated
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
networks:
- backend
# volumes
volumes:
dbdata:
driver: local
networks:
backend:
1. Create an empty Makefile
In your project root folder, create a Makefile
Note all Makefile
has to be named Makefile
, otherwise they will not work, don’t try to rename the file to something else, otherwise it will not work.
$ touch Makefile
this will create a file on your hard disk inside the project root folder with the name Makefile
, now let’s start adding some commands in the next step.
2. Let’s add the below lines
# In order to help running the project with less intervention and dynamically,
# setting some variables to be used inside the makefile,
# some of them are to prefix commands specially for commands to be run inside a container
DOCKER_COMPOSE = docker-compose
PROJECT = "Monitoring API."
COMPOSER ?= composer
PHP_CMD = php
# The php service defined in the docker-compose.yml
PHP_SERVICE = api
# the below line make sure the service name are prefixed with the folder name
COMPOSE_PROJECT_NAME ?= $(notdir $(shell pwd))
# Here the command is determined based on the environment
# I'm not using docker in production
ifeq ($(APP_ENV), prod)
CMD :=
else
CMD := docker-compose exec $(PHP_SERVICE)
endif
# If you type make in your cli, it will list all the available commands.
help:
@ echo "Usage: make \<target\>\n"
@ echo "Available targets:\n"
@ cat Makefile | grep -oE "^[^: ]+:" | grep -oE "[^:]+" | grep -Ev "help|default|.PHONY"
# This will stop all containers services
container-stop:
@echo "\n==> Stopping docker container"
$(DOCKER_COMPOSE) stop
# This will remove all containers services
container-down:
@echo "\n==> Removing docker container"
$(DOCKER_COMPOSE) down
# Start the containers and build them every time this commands is called
container-up:
@echo "\n==> Docker container building and starting ..."
$(DOCKER_COMPOSE) up --build -d
# This is a shortcut command to stop and remove containers
tear-down: container-stop container-down
As you can see above, we have built a basic makefile
to run docker-compose
commands without typing much.
Now, if you want to run any of the above commands, just prefix it with make
$ make container-up
3. Let’s make things complex little bit
Let’s make our project’s makefile
more advanced and add some make
commands to:
- install composer dependencies
- Lint your project files.
- Check your project files if they follow the correct coding standards
and in order to do this add the below lines at the end of your Makefile
you just created.
# This will run inside the api container to install all composer dependencies.
composer-install:
@echo "\n==> Running composer install, runner $(RUNNER)"
$(CMD) $(COMPOSER) install
# This will run tests
tests:
@echo "\n==> Running tests"
$(CMD) bin/phpunit
# This will run coverage
coverage:
@echo "\n==> Generating coverage report"
$(CMD) bin/phpunit --coverage-html coverage
# This will lint all your project
lint: lint-json lint-yaml lint-php phpcs lint-composer lint-eol
@echo All good.
# This will validate line endings of your file
lint-eol:
@echo "\n==> Validating unix style line endings of files:"
@! grep -lIUr --color '^M' app/ web/ src/ composer.* || ( echo '[ERROR] Above files have CRLF line endings' && exit 1 )
@echo All files have valid line endings
# This will lint your composer.json and composer.lock
lint-composer:
@echo "\n==> Validating composer.json and composer.lock:"
$(CMD) $(COMPOSER) validate --strict
# This will lint your *.json* files if any, in the `src` folder
lint-json:
@echo "\n==> Validating all json files:"
@find src -type f -name \*.json -o -name \*.schema | php -R 'echo "$$argn\t\t";json_decode(file_get_contents($$argn));if(json_last_error()!==0){echo "<-- invalid\n";exit(1);}else{echo "\n";}'
# This will lint your *.yml* files if any, in the `src` and `app/config` folders
lint-yaml:
@echo "\n==> Validating all yaml files:"
@find app/config src -type f -name \*.yml | while read file; do echo -n "$$file"; $(CMD) php bin/console --no-debug --no-interaction --env=test lint:yaml "$$file" || exit 1; done
# This will lint your *.php* files in the `src` folder
lint-php:
@echo "\n==> Validating all php files:"
$(CMD) find src -type f -iname '*php' -exec $(PHP_CMD) -l {} \;
# If you have defined `phpcs.xml` and installed
# the dependant package (https://github.com/squizlabs/PHP_CodeSniffer)
# you can make use of the below command to check if your code has styling errors
phpcs:
@echo "\n==> Checking php styles"
$(CMD) vendor/bin/phpcs --standard=phpcs.xml -p
# This will fix all your styling standard issues
phpcbf:
@echo "\n==> Fixing styling errors"
$(CMD) vendor/bin/phpcbf
4. Let’s handle database
Now, in our project we are working with databases, so we need to shortcut some Symfony doctrine ORM commands to interact with our databases, below we are going to append some useful make
commands, which I consider the most used commands during development.
You can adjust them to your liking.
# These commands are very project specific
# Create your database
orm-database-create:
@echo "\n==> Creating database"
$(CMD) php bin/console doctrine:database:create
# Drop your database
orm-database-drop:
@echo "\n==> Dropping database"
$(CMD) php bin/console doctrine:database:drop --force
# Create your database schema
orm-schema-create:
@echo "\n==> Creating schema"
$(CMD) php bin/console doctrine:schema:create
# Drop your database schema
orm-schema-drop:
@echo "\n==> Dropping schema"
$(CMD) php bin/console doctrine:schema:drop --force
# Generate database migration based on the difference
# between your existing schema and mapping files
orm-migrations-generate-diff:
@echo "\n==> Generating migration diff"
$(CMD) php bin/console doctrine:migrations:diff
# Migrate your database to the next version
orm-migrations-migrate:
@echo "\n==> Executing migration"
$(CMD) php bin/console doctrine:migrations:migrate
Advantages
- Time saver if your run any of the above commands many times during development.
- You don’t have to
ssh
to the container just to run commands. - Abstracts commands
- It can be environment agnostic, by using environment variables.
- It can be used easily in your CI/CD pipelines.
- You can create
Makefile
templates for every project type you most work with, then it works out of the box, as you don’t have to repeat your self.
Disadvantages
- You spend some time in the beginning of your project to setup your
Makefile
. - It can get complex, if you increase the amount of logic inside your
Makefile
, and you start to get the feeling your programming inmake
.
Conclusion
As you can, you can go nuts with your makefile
and make adapt whatever command you like, and make it easier to run commands inside your docker
container.
But this doesn’t mean you can get ride of running docker-compose exec api bash
to switch to the terminal
of the container, the above is intended to save time and effort in writing too much in the cli.
You can also, make use of makefile
in any of the projects you are working on, I use it for my frontend projects as well.
References
If you would like to learn more about Makefile
and how to use/extend them, you will easily find a lot of resources on the internet. But, I recently stepped on a very handy website called MakefileTutorial, which has a lot of examples on how to write extensive Makefile
with examples as well.