Comprehensive Practical Guide to Linux Makefile Usage with Step-by-Step Examples
Makefile Core Structure
Each Makefile consists of a set of build rules, following this standard format:
<targets>... : <prerequisites>...
<tab><commands>
- Targets: Can be the name of an output file to generate, or a label for an action (e.g.
cleanfor file removal). - Prerequisites: Files or other targets that must exist or be updated before the target can be built.
- Commands: Shell operations executed to generate the target from its prerequisites. All command lines must be prefixed with a single tab character, not spaces.
Sample minimal Makefile for a C++ program:
CXX = g++
app: main.cc
$(CXX) -o $@ $^
.PHONY: clean
clean:
rm -f app
Note: On Linux, C++ source files may use .cc, .cpp, or .cxx extensions interchangeably. The g++ compiler handles C++ syntax, while gcc is used for C code.
Makefile Execution Workflow
- Dependency Validation: Before building a target, make first checks if all prerequisites exist. If any prerequisite is missing, make searches for a rule to generate that missing file first.
- Freshness Check: If all prerequisites exist, make compares the modification timestamp of each prerequisite against the target. If any prerequisite has a newer timestamp than the target, the target is marked for rebuild.
- Command Execution: For targets marked for update, make runs the associated command sequence as defined in the rule.
Common make Command Options
Default make execution
Running make without arguments executes the first target defined in the Makefile. For example, given the following Makefile:
client: client.cc
g++ -o client client.cc
server: server.cc
g++ -o server server.cc
Only the client target will be built when running make with no arguments.
make clean
The clean target is conventionally used to delete compiled object files, binaries, and other build artifacts.
make -f
Use this option to specify a non-default Makefile name. For example, to use a build definition named production_build.mk, run:
make -f production_build.mk
- When using
-f, make will not search for the defaultMakefile,makefile, orGNUmakefilefiles unless explicitly specified. - Multiple
-fflags can be passed to combine rules from multiple Makefiles. If duplicate rules are defined across files, later definitions override earlier ones. This is useful for splitting build configurations across different environments or feature sets.
make -C
Changes the working directory to the specified path before loading the Makefile. For example, given this project structure:
ecommerce_platform/
├── Makefile
└── services/
├── Makefile
├── payment/
└── inventory/
To run the Makefile in the services directory from the project root, execute:
make -C services
make -n
Prints all commands that would be executed for a target, without actually running them. This is useful for debugging build rules without modifying the filesystem.
make -s
Runs build commands in silent mode, suppressing command echo to standard output.
Makefile Variables
Variables reduce repetition in build rules, and are referenced using the $(VARIABLE_NAME) syntax.
Variable Assignment Operators
=(Recursive Expansion): Variable values are evaluated lazily, meaning the value is resolved every time the variable is referenced, not at assignment time.:=(Simple Expansion): Variable values are evaluated immediately at assignment time, and remain fixed unless explicitly modified later. Use theoverridedirective to prevent command-line variable assignments from overriding values defined in the Makefile.
Custom Variables
You can define custom variables to store compiler paths, build flags, and other repeated values:
CXX = g++
BUILD_FLAGS = -Wall -O2
app: main.cc
$(CXX) $(BUILD_FLAGS) -o app main.cc
Automatic Variables
Make provides built-in automatic variables that reference values from the current rule:
$@: The target name of the current rule$<: The first prerequisite in the current rule$^: All prerequisites in the current rule, with duplicate entries removed
Sample 1: Single source file build
app: main.cc
g++ -o $@ $<
Equivalent to:
app: main.cc
g++ -o app main.cc
Sample 2: Multi-object link step
app: io.o utils.o main.o
g++ -o $@ $^
Equivalent to:
app: io.o utils.o main.o
g++ -o app io.o utils.o main.o
Pattern Matching with %
The % wildcard defines generic pattern rules that apply to all files matching a given suffix. For example, this rule compiles any C++ source file to a corresponding object file:
CXX = g++
CXXFLAGS = -Wall -c
%.o: %.cc
$(CXX) $(CXXFLAGS) $< -o $@
.PHONY Directive
.PHONY declares pseudo-targets, which are labels for actions rather than actual output files. Marking a target as phony tells make to skip checking for a file with the target name, and always run the associated commands when the target is called, even if a file with that name exists in the working directory.
The all target is a conventional pseudo-target that lists all top-level build outputs as prerequisites. Running make without arguments will build all dependencies of the all target if it is the first rule defined.
Sample pseudo-target declaration:
.PHONY: all deploy clean
all: client server
deploy:
scp client server user@production-host:/opt/app/
clean:
rm -f client server *.o
To run the pseudo-targets, call:
make all && make deploy && make clean
Sample Clean Rule
This clean rule recursively removes all object files from the current directory and subdirectories:
.PHONY: clean
clean:
rm -f $(shell find ./ -name "*.o") client server
Sample Multi-Binary Build Rule
This Makefile builds two separate executables from their respective source files:
all: client server
client: client.cc network.cc
g++ -o $@ $^
server: server.cc network.cc
g++ -o $@ $^
clean:
rm -f client server *.o
.PHONY: all clean
Tip: Chain commands with semicolons to run a clean followed by a full build in one line:
make clean; make
Tip: Prefix commands with
@to suppress command echo. This is useful for multi-step administrative tasks:.PHONY: build_all build_all: @cd ./auth-service; make; cd ../ @cd ./order-service; make; cd ../
Common Built-in Functions
wildcard
The wildcard function returns a list of files in the current directory matching the specified pattern:
# Get all .cc files in the current working directory
SRCS = $(wildcard *.cc)
To recursively find files in subdirectories, use the shell function with find:
# Find all .cc files in the ./src directory and all its subdirectories
SRCS := $(shell find ./src -name "*.cc")
patsubst
The patsubst (pattern substitution) function replaces substrings matching a pattern in a given text string:
# List of source files
SOURCES = auth.cc payment.cc common.h
# Replace all .cc extensions with .o to get object file names
OBJECTS = $(patsubst %.cc, %.o, $(SOURCES))
Practical Makefile Samples
Single Source File Build
This Makefile builds an executable named app from main.cc:
app: main.cc
g++ -o $@ $^
.PHONY: clean
clean:
rm -f app
Run make to build the executable, and make clean to remove it.
Multi-Binary Build
This Makefile builds two separate executables for client and server:
all: client server
client: client.cc
g++ -o $@ $^
server: server.cc
g++ -o $@ $^
clean:
rm -f client server *.o
.PHONY: all clean
Note: The .PHONY declaration can be placed anywhere in the Makefile.