Basic Structure
A Makefile consists of rules. Each rule has three parts:
target: dependencies
command
command
- target — Usually a file name to create, or a phony target name
- dependencies — Files that the target depends on
- command — Shell commands to run (MUST be indented with TAB, not spaces)
Important: Commands must be indented with a TAB character, not spaces!
Your First Makefile
# Simple Makefile
hello:
echo "Hello, World!"
clean:
rm -f *.o
# Run the default (first) target
make
# Run a specific target
make hello
make clean
Phony Targets
Phony targets are commands, not files. Declare them with .PHONY to avoid conflicts with files of the same name:
.PHONY: clean test build deploy
clean:
rm -rf build/ dist/
test:
npm test
build:
npm run build
deploy: build
./deploy.sh
Variables
# Define variables
CC = gcc
CFLAGS = -Wall -g
SRC_DIR = src
BUILD_DIR = build
# Use variables with $(NAME) or ${NAME}
compile:
$(CC) $(CFLAGS) -o $(BUILD_DIR)/app $(SRC_DIR)/main.c
# Override variables from command line
# make compile CC=clang
Automatic Variables
# $@ = target name
# $< = first dependency
# $^ = all dependencies
# $* = stem of pattern match
%.o: %.c
$(CC) -c $< -o $@
# This compiles file.c to file.o
Dependencies
Make only rebuilds targets when dependencies are newer:
app: main.o utils.o
gcc -o app main.o utils.o
main.o: main.c header.h
gcc -c main.c
utils.o: utils.c header.h
gcc -c utils.c
# If header.h changes, both .o files rebuild
# If only utils.c changes, only utils.o rebuilds
Pattern Rules
Use % as a wildcard to create generic rules:
# Compile any .c file to .o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Build any executable from its .o file
%: %.o
$(CC) $< -o $@
Practical Examples
Node.js Project
.PHONY: install dev build test clean deploy
# Default target
all: install build
install:
npm ci
dev:
npm run dev
build:
npm run build
test:
npm test
lint:
npm run lint
clean:
rm -rf node_modules dist build
deploy: build
rsync -avz dist/ user@server:/var/www/app/
Python Project
.PHONY: venv install test lint clean
PYTHON = python3
VENV = venv
PIP = $(VENV)/bin/pip
venv:
$(PYTHON) -m venv $(VENV)
install: venv
$(PIP) install -r requirements.txt
install-dev: install
$(PIP) install -r requirements-dev.txt
test:
$(VENV)/bin/pytest
lint:
$(VENV)/bin/flake8 src/
clean:
rm -rf $(VENV) __pycache__ .pytest_cache
Docker Project
.PHONY: build run stop logs clean
IMAGE_NAME = myapp
CONTAINER_NAME = myapp-container
build:
docker build -t $(IMAGE_NAME) .
run: build
docker run -d --name $(CONTAINER_NAME) -p 3000:3000 $(IMAGE_NAME)
stop:
docker stop $(CONTAINER_NAME)
docker rm $(CONTAINER_NAME)
logs:
docker logs -f $(CONTAINER_NAME)
shell:
docker exec -it $(CONTAINER_NAME) /bin/sh
clean: stop
docker rmi $(IMAGE_NAME)
C/C++ Project
CC = gcc
CFLAGS = -Wall -Wextra -g
SRC_DIR = src
BUILD_DIR = build
SOURCES = $(wildcard $(SRC_DIR)/*.c)
OBJECTS = $(SOURCES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
TARGET = $(BUILD_DIR)/myapp
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJECTS) | $(BUILD_DIR)
$(CC) $(OBJECTS) -o $@
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
clean:
rm -rf $(BUILD_DIR)
Self-Documenting Help
Add a help target that lists available commands:
.PHONY: help
help: ## Show this help message
@echo "Available targets:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'
build: ## Build the application
npm run build
test: ## Run tests
npm test
deploy: ## Deploy to production
./deploy.sh
$ make help
Available targets:
help Show this help message
build Build the application
test Run tests
deploy Deploy to production
Tips & Best Practices
- Always use
.PHONYfor non-file targets - Use
@prefix to suppress command echo:@echo "quiet" - Use
-prefix to ignore errors:-rm file.txt - Use
$(MAKE)instead ofmakefor recursive calls - Keep it simple—complex logic belongs in shell scripts
# Suppress command output
quiet:
@echo "This command is not printed"
# Ignore errors (continue if rm fails)
clean:
-rm -f *.o
-rm -f app
# Recursive make
subdirs:
$(MAKE) -C subdir
Resources
Need help with build automation? Contact us for consulting.