alpha

Makefile

Quick reference for Makefile patterns and commands

#make#build#automation

Using make as Project Task Runner

make is a build automation tool that executes targets defined in a Makefile. Common use: standardize project commands across repositories.

make            # Run default target
make dev        # Run specific target

Mark targets as .PHONY when they don’t produce files:

.PHONY: dev
dev:
	uv run python main.py

Common Makefile Targets for Development Workflows

.PHONY: dev
dev:
	uv run uvicorn main:app --reload

.PHONY: run
run:
	uv run uvicorn main:app

.PHONY: build
build:
	uv run python -m build

.PHONY: test
test:
	uv run pytest -v

.PHONY: lint
lint:
	uv run ruff check .

.PHONY: fmt
fmt:
	uv run ruff format .

.PHONY: check
check: lint test

.PHONY: clean
clean:
	rm -rf dist/ .venv/ __pycache__/

Using Variables and Environment in Makefile

APP_NAME := app
ENV ?= dev

.PHONY: deploy
deploy:
	ENV=$(ENV) ./deploy.sh

.PHONY: aws
aws:
	aws s3 ls --profile ${AWS_PROFILE}
	echo "Current dir: $$PWD"
  • ${VAR} - resolved by make at parse time
  • $$VAR - passed to shell, resolved at execution time

Override variables from command line:

make deploy ENV=prod

Pass shell environment variables to make:

export AWS_PROFILE=production
make deploy

Understanding Shell Execution in Makefile

$(shell ...) executes at parse time when make reads the file, not during target execution:

# Runs immediately when Makefile is parsed
VERSION ?= $(shell git describe --tags --always --dirty)

This means all $(shell ...) calls complete before any target executes.

Conditionals and Assertions in Makefile

Conditionals:

ENV ?= dev

ifeq ($(ENV),prod)
    DOCKER_TAG := latest
else
    DOCKER_TAG := dev
endif

Assertions:

.PHONY: deploy
deploy:
ifndef AWS_PROFILE
	$(error AWS_PROFILE is not set)
endif
	./deploy.sh

Both are evaluated at parse time, not during target execution.

Calling Another Makefile from Makefile

Use $(MAKE) -C to run targets in subdirectories:

.PHONY: build
build:
	$(MAKE) -C api build
	$(MAKE) -C web build

.PHONY: test
test:
	$(MAKE) -C api test
	$(MAKE) -C web test

$(MAKE) ensures the same make binary is used. -C dir changes to directory before reading Makefile.

Building and Pushing Docker Images with Makefile

DOCKER := docker
DOCKERFILE := Dockerfile

DOCKER_REGISTRY := docker.io
DOCKER_IMAGE := $(DOCKER_REGISTRY)/username/app
DOCKER_TAG ?= $(shell git describe --tags --always --dirty)
DOCKER_PLATFORM ?= linux/amd64

.PHONY: build
build: ## Build the docker image
	$(DOCKER) build --platform=$(DOCKER_PLATFORM) -f $(DOCKERFILE) -t $(DOCKER_IMAGE):$(DOCKER_TAG) .

.PHONY: push
push: build ## Push the docker image
	$(DOCKER) tag $(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKER_IMAGE):latest
	$(DOCKER) push $(DOCKER_IMAGE):$(DOCKER_TAG)
	$(DOCKER) push $(DOCKER_IMAGE):latest

Self-Documenting Makefile with Help Target

Add ## description after target name, parse with awk:

.DEFAULT_GOAL := help

.PHONY: help
help: ## Show this help
	@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

Running make or make help outputs formatted list of targets with descriptions.

References