Reduce Cognitive Overhead by Automating with GNU Make

Every project carries a certain amount of mental burden around with it. If you’re working with an IDE, you have to remember where the menu options live, a handful of keyboard shortcuts, and the elusive position of that one row of pixels you have to hit with your mouse to resize the debug panel.

If you’re lucky enough to be working in text mode, you have to remember which build tool to use, a handful of its commands, the version control system and its extensive collection of commands, and a myriad of Vim keybindings. None of these are essential components of the complicated thing that you’re building, and all of them impose some degree of background mental load on each member of the team.

To minimize the daily mental load of working on a project, I automate as many frequent project interactions as I can with Make. Though its syntax can be a bit arcane and it usually won’t be your only build tool, Make is old, boring, and omnipresent—three traits I value highly in a tool that I use every day. And because it integrates so well with the shell you’re probably already using, it’s easy to write a thin wrapper around an existing toolchain.

For the Rails piece of a Rails + Ember.js project I’m working on, I started out with a few basic targets that look something like this:

# Basic Targets
    $(bundler) rails server

    $(bundler) rspec

    $(bundler) rake spec:ember_start_fg

    RAILS_ENV=development $(bundler) rake db:migrate; \
    RAILS_ENV=ember_test  $(bundler) rake db:migrate; \
    RAILS_ENV=test        $(bundler) rake db:migrate

# Settings
.PHONY : serve test test_server migrate
.SILENT : serve test test_server migrate

bundler = bundle exec

and on the Ember.js side:

# Basic Targets
    $(ember) serve

    $(ember) test

# Settings
.SILENT : serve test
.PHONY : serve test

ember = ./node_modules/ember-cli/bin/ember

Having these Makefiles in the repo gives me a consistent way to instantiate a Rails server and an Ember test server without thinking (and more importantly, scripts acting on my behalf). Most of the time, I don’t need to know which testing framework or soupe du jour JavaScript build tool we’re using under the hood. I can set up the commands to run them once and then deal with a more consistent interface in my day-to-day routine.

From the Rails directory, I run make serve to start up a server. When I’m working on Ember, I do the same thing: make serve. It’s a trivial thing, but having one set of administration commands to remember instead of two helps minimize disruptions and preserve flow.

It gets even better when you work on multiple projects. On a recent Python + Django project, for instance, I had a Makefile that looked something like this:

# Basic Targets
serve: clean
    echo "Starting local server..."
    source $(virtualenv); python runserver

test: clean
    echo "Running test suite..."
    source $(virtualenv); \
        if py.test; then \
            echo "All tests pass. You win at life."; \
            exit 0; \
        else \
            echo "Player 1 Game Over. Insert Coin..."; \
            exit 1; \

check: clean
    echo "Checking for problems with Django's built-in checker..."
    source $(virtualenv); python check
    echo "checked."

lint: clean
    printf "Checking for lint..."
    source $(virtualenv); flake8 .
    echo "clean."

# Build Environment Targets
    printf "Clearing out old pyc files..."
    find . -name "*.pyc" -exec rm -f {} \;
    echo "okay."

# Settings
.PHONY : serve test check lint clean
.SILENT : serve test check lint clean

virtualenv = ./venv/bin/activate

Even though these projects used different languages, frameworks, and toolchains, most of my interactions with them were conceptually the same: start a server (make serve); run a test suite (make test). By spending a few minutes building a Makefile for each project, I saved time and mental energy and made each project just a little bit nicer to work on.