A makefile tells the make utility how to compile and run programs. Instead of typing long commands every time you build your code, you write the instructions once and run a single command. Before starting, confirm that make is installed on your system.
How a Makefile Works: Rules, Targets, and Prerequisites
Create an empty directory called myproject. Inside it, create a file named Makefile with this content:
say_hello:
echo "Hello World"
Run it by typing make inside that directory:
$ make echo "Hello World" Hello World
Every rule in a makefile has three parts.
| Part | Description |
|---|---|
| Target | The name of what you’re building, or a label for a recipe |
| Prerequisites | Files or targets that the target depends on |
| Recipe | The commands that produce the target |
The general syntax is:
target: prerequisites <TAB> recipe
A target doesn’t have to be a file. When it’s just a label, it’s called a phony target. The full command prints before its output by default. To suppress that, add @ before the command:
say_hello:
@echo "Hello World"
Now make shows only Hello World without printing the command itself.
Setting a Default Target in a Makefile
By default, make runs only the first target it finds. To change this, use .DEFAULT_GOAL:
.DEFAULT_GOAL := generate
This tells make to run generate instead of the first target. Since .DEFAULT_GOAL handles one target at a time, most projects use an all target to call multiple targets together. Here’s a more complete example:
.PHONY: all say_hello generate clean
all: say_hello generate
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt
The .PHONY declaration tells make that these targets aren’t actual files. Running make now executes both say_hello and generate. The touch command creates the files — see how it handles shell brace expansion in this context. The clean target should be called manually with make clean rather than including it in all.
Using Variables in a Makefile to Avoid Hardcoded Values
Hardcoded values make makefiles fragile. Variables let you reuse values and change them in one place.
Use = to define a recursive expanded variable:
CC = gcc
hello: hello.c
${CC} hello.c -o hello
Both ${CC} and $(CC) are valid references. Assigning a variable to itself with =, though, causes an infinite loop. Use := instead — called a simply expanded variable — which evaluates the right side once at assignment:
CC := gcc
CC := ${CC}
This is the safer default. Reserve = for cases where you specifically need the value to re-evaluate every time it’s referenced.
Makefile Pattern Rules and Functions for Automating C Builds
This makefile compiles all .c files in a directory automatically:
.PHONY = all clean
CC = gcc
LINKERFLAG = -lm
SRCS := $(wildcard *.c)
BINS := $(SRCS:%.c=%)
all: ${BINS}
%: %.o
@echo "Checking.."
${CC} ${LINKERFLAG} $< -o $@
%.o: %.c
@echo "Creating object.."
${CC} -c $<
clean:
@echo "Cleaning up..."
rm -rvf *.o ${BINS}
Here’s what each part does.
| Line | Purpose |
|---|---|
$(wildcard *.c) | Collects all .c files into SRCS |
$(SRCS:%.c=%) | Substitution reference — strips .c extensions to get binary names |
%: %.o | Pattern rule — builds each binary from its matching object file |
%.o: %.c | Compiles each .c file into an object file |
$< | Refers to the first prerequisite |
$@ | Refers to the current target |
For a single file foo.c, the makefile expands to build foo.o from foo.c, then link foo.o into the final foo binary. The clean target removes all generated files. Once you have compiled binaries, you’ll need to set the right executable permissions with chmod before they can run.
Automatic Variables in Makefile Recipes
The automatic variables $@ and $< are the two you’ll use most often, but make provides others that come up in larger projects.
| Variable | Expands to |
|---|---|
$@ | The target name of the current rule |
$< | The first prerequisite |
$^ | All prerequisites, deduplicated |
$? | Prerequisites newer than the target |
$* | The stem matched by a pattern rule |
The $? variable is useful when you want a recipe to run only for changed files rather than all prerequisites. You can verify what make will run before executing it by passing -n, which prints the commands without running them. For anything involving recursive directories or environment variables, the grep command can help you quickly search through generated output or log files during debugging.
Common Makefile Mistakes to Avoid
The recipe line must start with a tab character, not spaces. This is the single most common source of confusing errors — make will report “missing separator” without pointing to the actual problem. Most editors let you configure this per-file type.
Leaving non-file targets out of .PHONY causes make to skip them when a file with that name exists. A clean target without .PHONY does nothing if a file named clean is present in the directory.
Using = when := is more appropriate can produce subtle bugs, especially when variables reference each other. If your build produces unexpected output, check variable assignments first.
FAQs
What is a makefile used for?
A makefile tells the make utility how to build a project. It defines which commands to run, in what order, and which files depend on which others — so only changed files get recompiled.
What is .PHONY in a makefile?
.PHONY marks targets that are not actual files. It tells make to run the recipe regardless of whether a file with the same name exists in the directory.
What is the difference between = and := in a makefile?
= defines a recursively expanded variable — evaluated each time it’s used. := evaluates once at assignment. Use := by default to avoid unexpected behavior.
How do you run a specific makefile target?
Run make target_name from the directory containing your Makefile. For example, make clean runs the clean recipe. Without a target name, make runs the default target.
Can a makefile compile multiple files at once?
Yes. Use $(wildcard *.c) to collect all source files automatically, then pattern rules like %.o: %.c to compile each one. The all target links the results.