Make for Web Developers
Make for Web Developers
The command line, it’s a powerful tool for us developers but it’s not the most user-friendly way to interact with a computer. It can be hard to remember steps in a multi-step process, hard to remember all the command line switches we need to have for our commands to run successfully, and hard on our bodies to type these all out over and over again. Thankfully, make
can make our lives easier.
In this article, we’ll discuss what make
is, how to create “Makefiles”, and how to help speed up your development workflow using make
.
What Is Make?
Generally, the make
command line tool is used to determine what portions of programs need to be recompiled so developers don’t need to recompile the whole source tree for every change. make
is generally used for any development environment where you have a “complex” local build, test, and install process. If you’re an experienced enough Linux user you may remember installing software from source using make
for every package that wasn’t part of the main distro.
The make
command line tool consumes Makefiles which are used to create links between target files and their inputs, which the make
command line tool then uses to determine if and what files it needs to build.
To run these examples, you’ll need a terminal and make
installed but chances are if you have a *nix environment you already have access to make
as it’s a required tool for a lot of software packages.
Why Use Make?
So you might be saying that’s great but I never need to install software from source anymore so why should I care?
As a PHP developer, I use make
for three things:
- Multi-step processes my team runs regularly (build processes)
- Complex commands we run occasionally (code coverage report)
- Any command we run a lot (pre-commit)
Another benefit is that make
is built to keep track of how input files are related to output files so it’s possible to structure your makefile to reduce the amount of time repeated commands take.
Make Basics
make
stores its configuration in “Makefile”s which are a specially formatted text files. By default. the make
command is going to look for a file named “Makefile” in the current directory but we can change this to fit our team’s needs.
On our team, we have a “Makefile” at the root of our project that contains things that can (and should) be used by everyone on our team. This file is included in our source control so we can keep it up to date across the team and can even be dependent on the branch we’re working in (during upgrades this can be super helpful).
“`
Uses default Makefile
make
Hello Developers!
“`
We also allow a “Makefile.local” that’s ignored by source control that can contain commands specific developers find helpful for their local development purposes.
“`
Uses Makefile.local
make -f Makefile.local
Hello Local Developer!
“`
Let’s create a basic Makefile that just echo
s out “Hello Developers”.
hello-developers:
@echo "Hello Developers!"
Then we can run make
with no target and it will run echo out our message as it’s the first entry in the “Makefile”.
$ make
Hello Developers!
Let’s talk about how that worked.
Targets
A Makefile
consists of a series of rules with a rule looking like this:
targets: prerequisites
command1
command2
command3
Where:
* Targets are generally the name of the file this rule will create or modify
* Commands are a series of steps used to make the target. It’s important these start with a tab character and not spaces or we’ll get a syntax error
* Prerequisites are a list of space delimited targets. These can also be called dependencies. These prerequisites will need to be satisfied before the commands for the target are run
An interesting part of this target/dependency setup is that the prerequisites don’t actually need to be files they can be an alias to another command we want to run before this command runs.
For example, at least once a month we run an “update” on both our “composer.lock” and “package.lock” (which you’re doing too right…). We have packages that also require us to publish the newest version of their assets (Horizon in this case).
Our goal is to make this process as easy as possible for our developers but we also sometimes need to update one or the other so we have the following “Updates” section of our “Makefile”.
“`
Updates
updateall: updatecomposer updatenpm
updatecomposer:
php composer.phar update
php artisan horizon:publish
update_npm:
npm update
“`
We can update just “composer.lock” or just “package.lock” using make update_composer
and make update_npm
or we can update both using make update_all
because we have update_composer
and update_npm
as prerequisites. Then when someone eventually creates another package management system we can easily add it to the update_all
target by adding it as a new prerequisite.
Once we’ve updated our lock files we then need to distribute those changes to our team so they have the versions of the packages we’re running. Obviously, we don’t want to “install” our packages when there aren’t any changes as there’s a non-zero amount of processing involved in this.
To prevent this we’ll use a feature that make
brings to the table which is that it will check the modified dates of the target and prerequisites and if the target’s modified date is after the oldest modified date of the prerequisites it won’t run the commands again.
We’ll create a target called “composer.json” that has prerequisites of “composer.lock”. In the composer.json commands we’ll run composer install
and then touch composer.json
which will set the modified date of the “composer.json” file.
We’ll also create a target called “composer.lock” because it’s a prerequisite of “composer.json” but we’ll do nothing as nothing needs to be updated at this point.
composer.json: composer.lock
composer install
# "trick" to keep this from running again
touch composer.json
composer.lock:
## do nothing but need this as a target
The first time we run make composer.json
it will run the install process and touch the “composer.json”
“`
$ make composer.json
composer install output here
touch composer.json
“`
Then if we run it again we’ll get the message that “`composer.json’ is up to date.” and it won’t run.
$ make composer.json
make: `composer.json' is up to date.
This is what makes make
ideal for developers of complied languages but that doesn’t mean we can’t use it as well.
Variables
The last feature of make
we’re going to discuss is using variables.
I’m a fan of using heavy amounts of static code analysis (SCA) on my code to find potential bugs.
We can do this with make
using the following.
“`
Static Code Analysis Tools
static-code: phpcs phpstan
phpcs:
./vendor/bin/phpcs –standard=phpcs.xml app tests
phpcbf:
./vendor/bin/phpcbf –standard=phpcs.xml app tests
phpstan:
./vendor/bin/phpstan
“`
One of the things I hate about the process is that it takes “too long” to run all the static code analysis tools locally for all the files in my project and the feedback loop of pushing the files to our testing infrastructure is too slow of a feedback loops.
That’s why I like to run the SCA tools on just the changed files. Using variables in make
can make this a little easier.
First, we’re going to define a variable called CHANGED_PHP_FILES
that defines a run of git diff
to find the staged files that have been added or modified. We’re not actually running the command here we’re just creating a variable to keep it easy to reuse. Then we can call that command and pipe it through xargs so it will run phpcs
and phpstan
with just those files.
“`
CHANGEDPHPFILES = git diff –diff-filter=AM –staged –name-only app tests | grep “.php”
pre-commit: phpcschanged phpstanchanged
phpcschanged:
${CHANGEDPHP_FILES} | xargs vendor/bin/phpcs –standard=phpcs.xml
phpstanchanged:
${CHANGEDPHP_FILES} | xargs vendor/bin/phpstan analyze
“`
Then when we realize we need to add the database directory as well we can easily change it in one spot:
“`
CHANGEDPHPFILES = git diff –diff-filter=AM –staged –name-only app database tests | grep “.php”
pre-commit: phpcschanged phpstanchanged
phpcschanged:
${CHANGEDPHP_FILES} | xargs vendor/bin/phpcs –standard=phpcs.xml
phpstanchanged:
${CHANGEDPHP_FILES} | xargs vendor/bin/phpstan analyze
“`
We can also tie this into git’s pre-commit script so we run these tools automatically.
“`
.git/hook/pre-commit
make pre-commit
“`
“`
$ git commit -a
git diff –diff-filter=AM –staged –name-only app tests | grep “.php” | xargs vendor/bin/phpcs –standard=phpcs.xml
git diff –diff-filter=AM –staged –name-only app tests | grep “.php” | xargs vendor/bin/phpstan analyze
Note: Using configuration file /Users/scottkeck-warren/Projects/capa/phpstan.neon.
5/5 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
[OK] No errors
“`
Make Is Super Powerful
Make is a super powerful tool and we’ve barely scratched the surface of what it can do. Chances are if you’re attempting to automate your build process there’s a way that make
can make it easier.
I would love to hear from you in the comments to know other ways that your team is using make
on your web application development.
What You Need To Know
- Make is a command line tool generally used to build complied programs
- We can make use of it to make repeated tasks easier
- Targets provide the commands we can run
- Can use the modified date to prevent rerunning for the same target
Leave a comment
Use the form below to leave a comment: