In this article I want to look at the make utility. Yes, that make utility.
$ man make MAKE(1) LOCAL USER COMMANDS MAKE(1) NAME make - GNU make utility to maintain groups of programs
Can this humble utility simplify a web developer's life managing a complex system of front end assets1?
In the past, I've used tools like Grunt, Gulp, Ant, and PHP's own Assetic to do this. Nothing felt right. Complex configurations, new scripts, new dependencies. There must be a better way.
If anything is known about me, it is this: I'm lazy (proof). Don't make me do any extra work I don't have to do. Seriously. That previous sentence was only one word, that's how lazy I am. To me, laziness is an ideal. It's an ideal I can believe in, a rare gem awash in an ocean of ruthless pragmatism. But I digress.
Digression Averted, Make To The Rescue
Today, I want to talk about make. I want to talk about how make allows me to get work done faster so I can spend more time thinking about interesting problems, and less time building the plumbing for my web applications.
And, let's face it, plumbing carries shit. The less time I spend working on plumbing, the less time I have shit on my hands.
A Brief, Incomplete, Mostly Wrong History of Make
Make was originally introduced by Dr. Stuart Feldman of Bell Labs, in April 1976. Prior to creating make, Dr. Feldman was on the original team that created Unix. Afterward, he helped author the first Fortran 77 compiler. It is almost impossible to overstate the impact that Dr. Feldman has had on software development.
Prior to widespread adoption of make, software was distributed with OS-specific "make" and "install" scripts. Make allowed multiple targets with commands to be written uniformly into one standardized build script. And this elegant solution is still one of the most widely distributed tools ever almost 40 years later.
Great. How Does This Relate To My Job?
The essence of make is to define repeatable transformations from a source to a target.
Ok. So what does that mean?
Transformations are shell commands, grouped into logical tasks by task name.
The source and target are filesystem objects. So what we have is a collection of shell commands, organized into logical groups of tasks, which define transformations in the filesystem.
Anatomy of a Makefile for the Web
By convention, a makefile's first target is a default catch-all, usually
all: clean build
A web makefile is going to have different targets depending on things like libraries and the project structure. If dependencies are managed by Bower (and assuming bower is already configured and working), an
install target like the one below works:
install: bower install
buildDir = /var/www/public install: bower install \ r.js -convert $(buildDir)/js/react-dropzone/ $(buildDir)/js/react-dropzone/ \ mkdir -p $(buildDir)/js $(buildDir)/css \
Clean And Build
assetsDir = /var/www/assets buildDir = /var/www/public build: cp -r $(assetsDir)/dist/* $(buildDir)/js \ cp -r $(assetsDir)/dist/font-awesome/fonts $(buildDir)/fonts jsx --harmony --extension jsx --es6module $(assetsDir)/jsx $(buildDir)/js \ lessc $(assetsDir)/less/master.less $(buildDir)/css/master.css clean: rm $(buildDir)
Watching For Changes
Adding a --watch flag to the jsx command allows for automatic rebuild of JS resources when changed. Let's create a target for it.
watch: jsx --harmony --extension jsx --es6module --watch $(assetsDir)/jsx $(buildDir)/js
Putting It All Together
The makefile below contains two macros: assetsDir, buildDir, and four targets: install, build, watch, clean.
assetsDir = /var/www/assets buildDir = /var/www/public install: bower install \ r.js -convert $(assetsDir)/dist/react-dropzone/ $(assetsDir)/dist/react-dropzone/ \ mkdir $(buildDir)/js $(buildDir)/css build: cp -r $(assetsDir)/dist/* $(buildDir)/js \ cp -r $(assetsDir)/dist/font-awesome/fonts $(buildDir)/fonts \ jsx --harmony --extension jsx --es6module $(assetsDir)/jsx $(buildDir)/js \ lessc $(assetsDir)/less/master.less $(buildDir)/css/master.css watch: jsx --harmony --extension jsx --es6module --watch $(assetsDir)/jsx $(buildDir)/js clean: rm $(buildDir)