makefile tutorial. ai generated. 2026-06-11

GNU Make Tutorial: From Zero to Productive

GNU Make is a build automation tool that determines which parts of a large program need to be recompiled and issues the commands to recompile them. It is incredibly useful for C/C++, but also for any project involving files that depend on each other (LaTeX, documentation, websites, etc.).

1. Installation

# Debian/Ubuntu
sudo apt install make

# macOS (already installed via Xcode Command Line Tools)
xcode-select --install

# Fedora/RHEL
sudo dnf install make

# Windows (via MSYS2, WSL, or Chocolatey)
choco install make

Verify: make --version

2. Your First Makefile

Create a file named Makefile (or makefile) in your project root.

# Simple C program example
program: main.o utils.o
	gcc -o program main.o utils.o

main.o: main.c
	gcc -c main.c

utils.o: utils.c
	gcc -c utils.c

clean:
	rm -f *.o program

How to run it:

make          # builds the default target ("program")
make clean    # removes generated files
make program  # explicit target

3. Makefile Syntax Basics

Rule structure:

target ... : prerequisites ...
		command
		command

Comment with #

4. Variables (Macros)

CC = gcc
CFLAGS = -Wall -O2 -g
LDFLAGS = -lm

program: main.o utils.o
	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

Common automatic variables (very useful):

Variable Meaning Example value
$@ Target name program
$< First prerequisite main.c
$^ All prerequisites (no duplicates) main.o utils.o
$* Stem of pattern match main (for %.o)
$? Prerequisites newer than target (changed files)

5. Pattern Rules (The Real Power)

# Generic rule for all .o files from .c files
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Generic rule for all executables from .o files
%: %.o
	$(CC) -o $@ $^

With these two rules you can often delete most explicit rules.

6. Phony Targets

Tell Make that a target is not a file:

.PHONY: all clean install test

all: program

clean:
	rm -f *.o program

install: program
	cp program /usr/local/bin/

Without .PHONY, if you ever create a file named clean, make clean would do nothing.

7. Built-in Variables and Functions

SOURCES = $(wildcard *.c)
OBJECTS = $(SOURCES:.c=.o)          # substitution reference
EXEC = program

# Functions
HEADERS = $(wildcard *.h)
ALL_FILES = $(SOURCES) $(HEADERS)

$(EXEC): $(OBJECTS)
	$(CC) -o $@ $^

# String functions
OBJDIR = obj
$(OBJECTS): | $(OBJDIR)             # order-only prerequisite
	$(CC) -c $(CFLAGS) $(addprefix -I,$(INCLUDES)) $< -o $@

$(OBJDIR):
	mkdir -p $@

8. Conditional Parts

ifeq ($(DEBUG),1)
    CFLAGS += -DDEBUG -g
else
    CFLAGS += -O2
endif

ifdef VERBOSE
    MAKEFLAGS += --debug
endif

9. Including Other Makefiles

include config.mk
-include deps.mk          # don't fail if file missing

10. Complete Modern Example

# ========================================
# Project settings
# ========================================
NAME     = myapp
CC       = gcc
CFLAGS   = -Wall -Wextra -std=c11 -O2
LDFLAGS  =

SRC_DIR  = src
OBJ_DIR  = obj
BIN_DIR  = bin

SOURCES  = $(wildcard $(SRC_DIR)/*.c)
OBJECTS  = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SOURCES))

# ========================================
# Targets
# ========================================
.PHONY: all clean

all: $(BIN_DIR)/$(NAME)

$(BIN_DIR)/$(NAME): $(OBJECTS) | $(BIN_DIR)
	$(CC) $(LDFLAGS) -o $@ $^

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
	$(CC) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
	mkdir -p $@

clean:
	rm -rf $(OBJ_DIR) $(BIN_DIR)

# Automatic dependency generation (advanced but worth it)
DEPFLAGS = -MMD -MP
CFLAGS += $(DEPFLAGS)
-include $(OBJECTS:.o=.d)

11. Useful Command-Line Options

make -j4            # parallel build (4 jobs)
make -j$(nproc)     # use all CPU cores
make -n             # dry-run (show commands)
make -B             # force rebuild everything
make -f alt.mk      # use different makefile
make V=1            # many projects show full commands when V=1

12. Best Practices

  1. Always use .PHONY for non-file targets.
  2. Prefer pattern rules over explicit rules.
  3. Put object files in a separate directory.
  4. Generate dependency files with -MMD -MP.
  5. Use variables for tools and flags (easy to override: make CFLAGS="-g -fsanitize=address").
  6. Keep the Makefile readable — future you will thank you.

Quick Reference Cheat Sheet

Now go create a Makefile in one of your projects and replace your manual build scripts!

Once you're comfortable with the basics, explore advanced topics like: