Makefiles 101: Automating Your Workflow with `make`

Makefiles 101: Automating Your Workflow with make

Makefiles are a powerful but often overlooked tool for automating tasks in development workflows. Whether you're managing dependencies, running scripts, or cleaning up files, make can simplify and streamline repetitive tasks.


Anatomy of a Makefile

A Makefile consists of targets, prerequisites, and recipes:

setup: deps.get yarn
	$(run) mix ecto.reset

setup: Target

  • A file or directory that needs to be built.

deps.get yarn: Prerequisites

  • Other files or targets that must be completed before the target can be built.

$(run) mix ecto.reset: Recipe

  • A series of shell commands executed to build the target.
  • Must be preceded by a tab, not spaces.

When we run make setup, make first ensures the prerequisites (deps.get and yarn) are built. Then, it executes the commands to build the setup target.

make will rebuild a target if it doesn’t exist or if any prerequisites have changed.


Phony Targets

A phony target is a target that doesn’t correspond to an actual file but instead serves as a label for a set of commands:

clean:
	rm *.o temp

If a file named clean ever appears in the directory, make will assume it’s up to date and won’t execute the recipe. To avoid this issue, declare it as a phony target:

.PHONY: clean

clean:
	rm *.o temp

Now, make clean will always execute the cleanup commands.


Variables in Makefiles

Makefiles support different types of variable assignment:

foo = bar   # Regular assignment
foo ?= wat  # Assigns only if not already set
foo := boo  # One-time immediate assignment
  • = assigns a value but allows later changes.
  • ?= assigns only if the variable hasn’t been set.
  • := assigns immediately and won’t change later.

Passing Arguments to Make Targets

Make doesn’t support function-style arguments, but you can extract them from MAKECMDGOALS:

args := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))

add-migration:
	bundle exec rails g migration $(args)

Now, you can pass arguments like:

make add-migration add_deleted_at_to_users deleted_at:datetime

However, this approach has limitations (e.g., limited argument count). If flexibility is needed, consider using shell scripts instead.


Makefile Cheatsheet

CommandDescription
@Suppress printing the actual command, only show results

Help Target

A common convention is adding a help target that greps comments to list available commands:

help:
	@echo
	@echo "Makefile targets:"
	@grep -E '^[a-zA-Z_-].*?: .*?## .*$$' Makefile | sed 's#\\:#:#g' | awk 'BEGIN {FS = ": .*?## "}; {printf "\033[36m  %-20s\033[0m %s\n", $$1, $$2}'
	@echo

Usage:

make help

Example:

gql: ## Starts the GraphQL server
	$(graphql)

References