Makefile for lazy developers
Whatever the size of the software project, I believe in, subscribe to, and promote Continuous Integration. Personally, I rely on GitHub Actions as an automated build system. Even this blog here is built with, and eventually deployed into production using GitHub Actions.
Regardless of whether an automated build system can be set up and used for a project or not, I prefer to be able to run build steps locally. This prevents stress-testing the automated build system and taking away resources from other developers. Also, it gives me more confidence before committing and pushing changes upstream.
The first time I heard of an automated build tool was when I was working on a crowd investment project in 2011. Back then a developer set up Capistrano, and I didn't understand a thing. Then had used Phing for a while. For a couple of years now I have been using make
, after having been introduced to it when working on a project in 2014. While it has its limitations, it's short and simple, and most of all, it get's the job done.
In most of my projects I use a Makefile
similar to the following:
.PHONY: it
it: coding-standards static-code-analysis tests
.PHONY: code-coverage
code-coverage: vendor
vendor/bin/phpunit --configuration=test/Unit/phpunit.xml --coverage-text
.PHONY: coding-standards
coding-standards: vendor
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --show-progress=dots --verbose
.PHONY: mutation-tests
mutation-tests: vendor
vendor/bin/infection --configuration=infection.json
.PHONY: static-code-analysis
static-code-analysis: vendor
vendor/bin/psalm --config=psalm.xml --clear-cache
vendor/bin/psalm --config=psalm.xml --show-info=false --stats --threads=10
.PHONY: static-code-analysis-baseline
static-code-analysis-baseline: vendor
vendor/bin/psalm --config=psalm.xml --clear-cache
vendor/bin/psalm --config=psalm.xml --set-baseline=psalm-baseline.xml
.PHONY: tests
tests: vendor
vendor/bin/phpunit --configuration=test/Unit/phpunit.xml
vendor/bin/phpunit --configuration=test/Integration/phpunit.xml
vendor: composer.json composer.lock
composer validate --strict
composer install --no-interaction --no-progress
As you can see, I have defined a few phony targets, which may run any number of commands, or depend on so-called prerequisites.
For example, running
make tests
will first execute the vendor
target, followed by running unit and integration tests. Running
make coding-standards
will first execute the vendor
target, followed by running php-cs-fixer
. You get the idea.
A note on the vendor
target: I previously used to have a composer
target. After posting this article on Reddit, Nic Wortel provided an excellent suggestion:
I use the following recipe in my Makefiles:
vendor: composer.json composer.lock composer install
This will compare the timestamp of the
vendor
directory with those ofcomposer.json
andcomposer.lock
, and will only execute the recipe if either of the composer files is newer than thevendor
directory, or if the vendor directory is missing.
Excellent, this saves even more time!
Finally, I have added an it
target. Running
make it
will execute all of the targets I consider important enough to be run before I commit and push changes upstream.
Now, you might have noticed that while I have a penchant for keeping things sorted, I have intentionally defined it
as the first target. There's a good reason to it: unless a target has been specified explicitly, the first target defined in the Makefile
will be executed. That is, by making it
the first target, I can run
make
to execute all of the important targets.
Nonetheless, as I have gotten into the habit of running these tasks many, many (dozens? hundreds?) of times throughout the day, it's still a bit too much typing, right? Therefore, I have created an alias in my shell configuration:
# Makefile
alias m="make"
Running
m
achieves the same now. I believe that's the closest thing to making a build in one step, with only two key strokes.
Hope it helps to increase your productivity!