Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Software tooling & tips

Makefiles made easy: Key tips for beginners

A simple guide to Makefiles to make your development smoother and more approachable. Perfect for beginners!
Dave Mosher
|
April 16, 2017
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

It has been but a few short years since web developers chose a side and took up arms in the holy war of build automation tools; this is only one of many wars that have been fought countless times since the dawn of computing. In the grim darkness of the far future, there is only war.

… Meh. War is tiresome and I have had enough of war. This post is about some small usability improvements you can add to your Makefiles if you are using Make. Let’s dig in!

The Makefile

Make has been around for a long time. It has some neat features but it’s not always the friendliest to neophytes. Imagine you have a new developer joining your team, and your project has a Makefile that looks something like this:‍

VERSION ?= $(shell cat VERSION)

.PHONY: version clean bump release

build:
  @echo "building..."
  # build the app here

clean:
  # rm -rf build

release:
  # bump
  # make push -e VERSION=$(shell cat VERSION)

push:
  # push the build artifact at a given version somewhere

version:
  # cat VERSION

bump:
  # using semver, bump the version by a major, minor or patch increment

At first glance this Makefile isn’t all that complicated but chances are your build automation process is composed of many more lines of code or even split into multiple places. The wise aged veteran developer on your team tells the new member “to build this project just clone this repo and run make” after which the new developer sees:

neophyte@newbie:~/code/project
$ make
building...

Now, assuming new dev didn’t encounter any snags with project setup and installing dependencies (hah! unlikely) this still doesn’t present a great picture of how the project is assembled or the bits of the lifecycle that are involved at first glance. Let’s see if we can improve this initial Makefile developer experience.

Step 1: Add a DEFAULT_GOAL

In Make semantics, goals are targets that make should strive to update. The docs give us a nice explanation of goals as well as some hints about how we can manage which goal is run first:

By default, the goal is the first target in the makefile (not counting targets that start with a period). Therefore, makefiles are usually written so that the first target is for compiling the entire program or programs they describe. If the first rule in the makefile has several targets, only the first target in the rule becomes the default goal, not the whole list. You can manage the selection of the default goal from within your makefile using the .DEFAULT_GOAL variable (see Other Special Variables).

Even though the docs give us some informal conventions about the first target in our Makefile I think it makes (heh) for a better experience if we add some sort of help target that spits out some information to the terminal. Make doesn’t have any facility to display help messages like some other build automation tools, but it won’t be too hard to add one. First, let’s add a default goal of help:‍

.DEFAULT_GOAL := help

help:
  @echo "Welcome to the Project!"

Tip: prefixing a line in your make target with `@` suppresses output of that line to stdout.


With this in place our new developer sees the following:

neophyte@newbie:~/code/project
$ make
Welcome to the Project!

Ok, this is a little more friendly but still not very useful. We can do better!

Step 2: Annotate Makefile targets

Let’s update our Makefile to add helpful annotations to some of our targets using comment blocks prefixed with ##:‍

build: ## builds the application
  @echo "building..."

clean: ## gets you back to a clean working state
  # rm -rf build

release: bump ## bump the VERSION file, git tags, and push to github
  # make push -e VERSION=$(shell cat VERSION)

This annotation scheme works pretty well but doesn’t buy us anything on its own, to make this truly useful we need to parse the Makefile, look for lines prefixed with ## and format them in a pretty way and write them to stdout

Step 3: Parse annotations

Make includes a handy list of special variables that can be used for all sorts of handy things. In this case we can use the MAKEFILE_LIST variable along with grep, sort and awk to get a list of annotated targets and display them on stdout in a user-friendly way:‍

help:
  @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

Tip: Wondering what the `help` target is doing? See this [explain shell](https://explainshell.com/explain?cmd=grep+-E+%27%5E%5Ba-zA-Z_-%5D%2B%3A.*%3F%23%23+.*%24%24%27+%24%28MAKEFILE_LIST%29+%7C+sort+%7C+awk+%27BEGIN+%7BFS+%3D+%22%3A.*%3F%23%23+%22%7D%3B+%7Bprintf+%22%5C033%5B36m%25-30s%5C033%5B0m+%25s%5Cn%22%2C+%24%241%2C+%24%242%7D%27).


With that in place our new developer would run make from the command-line and see:

neophyte@newbie:~/code/project
$ make
build                          builds the application
clean                          gets you back to a clean working state
release                        bump the VERSION file, git tags, and push to github

Yay! This is a much nicer developer-experience than what we started with. To take it even further I might suggest annotating only a subset of tasks that are most commonly used.

Acknowledgements & resource links

There are a few sources that come up when you google for “self-documenting makefile” along with a few different ways of solving this problem. I drew inspiration for this post mostly from this marmelab entry, but felt like there were enough interesting points and general make tips added that it was worth another post.

Other links you may find useful:

  • Full Makefile as Gist
  • VERSION bump script as Gist
  • GNU Make
  • Self Documenting Makefiles
  • Explain Shell

Related Insights

🔗
Effective and easy strategies to hate bugs a little less
🔗
Code generation tools are easy to start, but hard to master
🔗
The looming demise of the 10x developer

Explore our insights

See all insights
Developers
Developers
Developers
C# and .NET tools and libraries for the modern developer

C# has a reputation for being used in legacy projects and is not often talked about related to startups or other new business ventures. This article aims to break a few of the myths about .NET and C# and discuss how it has evolved to be a great fit for almost any kind of software.

by
Patrick Coakley
Leadership
Leadership
Leadership
Turning observability into a team strength without a big overhaul

By addressing observability pain points one at a time, we built systems and practices that support rapid troubleshooting and collaboration.

by
Gabriel Côté-Carrier
Developers
Developers
Developers
Why I actually enjoy PR reviews (and you should, too)

PR reviews don't have to be painful. Discover practical, evidence-based approaches that turn code reviews into team-building opportunities while maintaining quality and reducing development friction.

by
Robert Komaromi
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.