Improving UX with Makefiles
Recently I updated the code for this website to use Makefiles and rely
less on one-off shell scripts. The reason for this change was to
improve my write-test-release workflow. I figured make
is available
on all of my computers and it serves as a perfect minimal orchestration
tool. I now type make deploy
instead of ./deploy.sh
to
push a new version of my website up to production. While this may not
seem like a lot, it reduced the number of files in my directory.
typicalrunt.me$ ls *.sh
build.sh
deploy.sh
notify.sh
s3-sync.sh
server.sh
The files are named after their purpose which is good, but I want
to remember less files in my project, since most of these files are
simple one-liners. For instance, this is build.sh
:
#!/bin/bash
bundle exec middleman build
I originally wrote this because I didn't want to type out the entire
bundle exec
sequence everytime I rendered my website to static files.
When I decided to move to Makefiles, I have a simple interface that takes
each of those one-liners and moves it into a Makefile target. Thus, the
above build.sh
file becomes in make:
build: deps
@bundle exec middleman build
What is that deps
target? It ensures that the system requirements are
met for running the bundle
command. Since I run Bundler in multiple
scripts, this means I can
DRY up the
dependency checks inside the Makefile, instead of duplicating it among
the shell scripts.
Here is my completed Makefile:
.DEFAULT_GOAL := help
.PHONY: build test deploy deps help
AWS_BIN := aws
BUNDLE_BIN := bundle
CURL_BIN := curl
BUCKET := REDACTED
SERVER := api.hipchat.com
ROOM_ID := REDACTED
URL := https://$(SERVER)/v2/room/$(ROOM_ID)/notification?auth_token=$(HIPCHAT_TOKEN)
deps:
@hash $(AWS_BIN) > /dev/null 2>&1 || \
(echo "Install aws to continue."; exit 1)
@hash $(BUNDLE_BIN) > /dev/null 2>&1 || \
(echo "Install bundler to continue."; exit 1)
@hash $(CURL_BIN) > /dev/null 2>&1 || \
(echo "Install curl to continue."; exit 1)
@test -n "$(HIPCHAT_TOKEN)" || \
(echo "HIPCHAT_TOKEN env must be set"; exit 1)
help:
@echo "Builds, tests, and deploys the static website files"
@echo ""
@echo "Targets:"
@echo " build Renders the static website"
@echo " help This message"
@echo " deploy Uploads static website to S3"
@echo " deps Ensures the system requirements are met"
@echo " test Starts a local server to view static website"
build: deps
@$(BUNDLE_BIN) exec middleman build
test: deps
@$(BUNDLE_BIN) exec middleman server
deploy: deps
@$(AWS_BIN) s3 sync build/ s3://$(BUCKET)
@$(CURL_BIN) -X POST \
-d @notification.json \
--header "Content-Type:application/json" \
$(URL)
With this I now have a simple UI to building, testing, and releasing new website articles:
typicalrunt.me$ make help
Builds, tests, and deploys the static website files
Targets:
build Renders the static website
help This message
deploy Uploads static website to S3
deps Ensures the system requirements are met
test Starts a local server to view static website
This also allowed me to remove all of the shell scripts and replace it with one Makefile, making an awesome commit:
typicalrunt.me$ git rm *.sh
typicalrunt.me$ git add Makefile
typicalrunt.me$ git commit -m 'Makefiles FTW!'
Makefiles aren't for everyone or every situation, but when you consider the cost of maintaining separate one-liner shell scripts, you might find that using a simple build tool provides better UX and less cognitive load.