Mastering Makefiles: From a Simple One‑Line Rule to Smart Automation
This step‑by‑step tutorial shows how to write a Makefile for a tiny C program, then progressively improves it with separate object files, variables, pattern rules, and built‑in functions like wildcard and patsubst, while explaining each concept and its benefits.
1. Introduction
After learning the basics of Makefiles, you can start writing your own. Although tools exist to generate Makefiles automatically, writing them manually deepens understanding and gives you full control over future project builds.
2. Sample Source Code
The example program consists of main.c that calls two functions defined in fun1.c and fun2.c:
// main.c
int main() {
printf("hello world
");
fun1();
fun2();
}
// fun1.c
void fun1() {
printf("this is fun1
");
}
// fun2.c
void fun2() {
printf("this is fun2
");
}3. First Version – A Single Rule
The whole program can be compiled with a single command: gcc main.c fun1.c fun2.c -o app Translating that command into a Makefile yields:
app: main.c fun1.c fun2.c
gcc main.c fun1.c fun2.c -o appThis version works for tiny projects but has two major drawbacks:
It does not scale to large codebases with many source files.
Any change to a single source forces a full recompilation.
4. Second Version – Separate Object Files
To avoid recompiling everything, each source file is compiled into an object file first:
main.o: main.c
gcc -c main.c -o main.oThe complete Makefile becomes:
app: main.o fun1.o fun2.o
gcc main.o fun1.o fun2.o -o app
main.o: main.c
gcc -c main.c -o main.o
fun1.o: fun1.c
gcc -c fun1.c -o fun1.o
fun2.o: fun2.c
gcc -c fun2.c -o fun2.oNow only the modified file needs recompilation, but the file list is still hard‑coded and contains duplicated patterns.
5. Third Version – Variables and Pattern Rule
Introduce variables to avoid repetition and a pattern rule to handle all object files uniformly:
obj = main.o fun1.o fun2.o
target = app
CC = gcc
$(target): $(obj)
$(CC) $(obj) -o $(target)
%.o: %.c
$(CC) -c $< -o $@The automatic variables $< (first prerequisite) and $@ (target) make the rule generic. Remaining issues:
The obj variable still requires manual updates when files are added or removed.
For projects with many sources, the variable becomes long and error‑prone.
6. Fourth Version – Wildcard and Patsubst Functions
Use Make’s built‑in functions to discover source files automatically and generate the object list:
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
# Equivalent: obj = $(src:%.c=%.o)
target = app
CC = gcc
$(target): $(obj)
$(CC) $(obj) -o $(target)
%.o: %.c
$(CC) -c $< -o $@
.PHONY: clean
clean:
rm -rf $(obj) $(target)The wildcard function finds all .c files in the current directory, while patsubst converts them to corresponding .o files. The .PHONY target clean removes intermediate and final binaries.
7. Conclusion
Writing Makefiles by hand reinforces the underlying concepts of incremental compilation and build automation. By progressing from a single‑line rule to a fully automated Makefile that discovers sources, defines variables, and uses pattern rules, you gain a solid foundation for managing larger C projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
