php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Make for Web Developers

Posted by on June 24, 2024

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:

  1. Multi-step processes my team runs regularly (build processes)
  2. Complex commands we run occasionally (code coverage report)
  3. 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 echos 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
update
composer:
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:
${CHANGED
PHP_FILES} | xargs vendor/bin/phpcs –standard=phpcs.xml

phpstanchanged:
${CHANGED
PHP_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:
${CHANGED
PHP_FILES} | xargs vendor/bin/phpcs –standard=phpcs.xml

phpstanchanged:
${CHANGED
PHP_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

  1. Make is a command line tool generally used to build complied programs
  2. We can make use of it to make repeated tasks easier
  3. Targets provide the commands we can run
  4. Can use the modified date to prevent rerunning for the same target

Tags: , ,
 

Leave a comment

Use the form below to leave a comment: