By mastering the variables, rules, implicit rules, and automatic variables of Makefile, you can transform scattered compilation commands into a stable, efficient, and reusable build pipeline.

1. Basic Syntax of Makefile (Minimal Working Example)
A simple Makefile:
CC = gcc
CFLAGS = -O2 -Wall
SRC = main.c util.c
OBJ = $(SRC:.c=.o)
all: app
app: $(OBJ)
$(CC)$(CFLAGS) -o $@$^
%.o: %.c
$(CC)$(CFLAGS) -c -o $@$<
clean:
rm -f $(OBJ) app
Key Points:
- • Variables are defined using = and referenced as $(VAR);
- • The pattern rule
<span>%.o: %.c</span>generates corresponding .o files for all .c files; - • Automatic variables: TableofContentsTarget,^ represents all dependencies, and $< represents the first dependency;
- •
<span>clean</span>is a common phony target, recommended to declare as<span>.PHONY</span>(see later).
2. Variables, Conditions, and Include
Extract variable content for reuse across CI/different platforms:
CC ?= gcc # Assign default value only if not defined externally
CFLAGS ?= -O2 -g
ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG -Og
endif
include common.mk
Explanation:<span>?=</span> assigns a value only if it is undefined, <span>ifeq</span> is used for conditional compilation of Makefile, and <span>include</span> allows splitting common rules into reusable files.
3. Pattern Rules and Automatic Variables (Reduce Redundancy)
Automatic variables are the soul of Make, and combined with pattern rules, they can compress repetitive commands into concise rules:
- • $@: Current target name;
- • $<: First dependency (commonly used in single dependency rules);
- • $^: All dependencies (deduplicated);
- • (@F): Directory/file name part of the target.Example: Rules for generating static and shared libraries
libmylib.a: $(OBJ)
ar rcs $@ $^
libmylib.so: $(OBJ)
$(CC) -shared -o $@ $^
4. Parallel Builds and Dependency Correctness
- • Use
<span>make -jN</span>to execute rules in parallel, where N is the number of CPU cores or greater; - • Ensure complete dependency declarations (changes in header files should trigger recompilation), which can be achieved using automatic dependency generation:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o\(:\),\1.o \2,g' $@.$$$$ > $@; \
rm -f $@.$$$$
-include $(SRC:.c=.d)
This snippet generates .d files and automatically includes them in the next Make, thus triggering a rebuild when header files change.
5. Common Targets and Conventions
- •
<span>all</span>: Default build target, usually depends on the final product; - •
<span>clean</span>: Removes intermediate products; - •
<span>install</span>: Copies files to system directories or DESTDIR; - •
<span>test</span>: Runs unit tests; - • Use
<span>.PHONY</span>to declare phony targets:
.PHONY: all clean install test
6. Advanced Techniques (Engineering Recommendations)
- • Place platform-specific variables in
<span>config.mk</span>and load them using<span>include</span>; - • In CI, treat
<span>make</span>as a task dispatcher:<span>make test</span>,<span>make lint</span>,<span>make package</span>; - • Use
<span>MAKEFLAGS += -j$(nproc)</span>to enable default parallelism in CI runners; - • Utilize
<span>$(shell ...)</span>to call external commands in Makefile (use cautiously, as it affects reproducibility); - • For large projects, consider layering builds (lib/, app/) and linking subprojects with a top-level Makefile.
7. Common Pitfalls and Debugging Methods
- • Command not executing: Check if the command line starts with a TAB;
- • Dependencies not triggering: Ensure dependency files (.d) are correctly generated and included;
- • Variables not taking effect: Confirm environment variable priority and the use of
<span>?=</span>; - • Target misjudged as up-to-date: Check timestamps or use
<span>make -B</span>to force rebuild.Debug commands:
make -n # Only print commands that will be executed
make -d # Debug mode, output detailed dependency resolution
8. Example: Reproducible Project Template
TOP = $(shell pwd)
CC = gcc
CFLAGS = -Wall -O2
SRCDIR = src
OBJDIR = build
SRC = $(wildcard $(SRCDIR)/*.c)
OBJ = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRC))
all: app
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC)$(CFLAGS) -c -o $@$<
$(OBJDIR):
mkdir -p $(OBJDIR)
app: $(OBJ)
$(CC)$(CFLAGS) -o $@$^
.PHONY: all clean
clean:
rm -rf $(OBJDIR) app
9. Summary
- • Learning to use variables and pattern rules can elevate Makefile from a script to a maintainable build system;
- • Keep dependencies accurate (automatically generate .d) to ensure correct incremental builds;
- • Agree on Makefile templates and include files within the team to reduce cross-project maintenance costs.