Understanding Makefiles: How They Work and Why You Need Them
Optimize Your Development Workflow with Make: A Guide to Makefiles
The make
utility is a powerful tool for streamlining your programming workflow, particularly useful for automating tasks that need to be triggered when files are updated. If you need to compile programs or execute specific actions based on file changes, make
is invaluable. It works by reading a file called Makefile
(or makefile
) that defines these tasks. You may have already used make
to compile code into executable binaries, which can then be installed with make install
.
In this article, we’ll guide you through both basic and advanced examples of using make
and Makefile
. Let’s get started!
Setting Up a Simple Makefile
To begin, let’s create a basic “Hello World” example using make
. Start by creating a directory called myproject
, and add a Makefile
inside with the following content:
say_hello:
echo "Hello World"
Now, navigate to myproject
and run make
:
make
echo "Hello World"
Hello World
In this example:
say_hello
is the target, similar to a function name in programming.echo "Hello World"
is the recipe.
Together, they form a rule, which is structured as follows:
target: prerequisites
<TAB> recipe
While say_hello
has no prerequisites in this case, targets often do. Targets may represent files to be generated or named tasks, also called phony targets.
To suppress command echoing, prefix the command with @
:
say_hello:
@echo "Hello World"
Run make
again:
$ make
Hello World
Adding More Functionality
Let’s add two more targets, generate
and clean
, to the Makefile
:
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt
Adding .PHONY
ensures make
runs these recipes even if files with these names exist:
.PHONY: all say_hello generate clean
all: say_hello generate
Now make
will run both say_hello
and generate
:
$ make
Hello World
Creating empty text files...
To remove generated files, use make clean
:
$ make clean
Cleaning up...
Advanced Makefile Techniques
Variables
Using variables can make your makefiles more flexible. Assign commands and flags to variables as shown below:
CC = gcc
hello: hello.c
${CC} hello.c -o hello
Variables like $(CC)
help avoid repetitive hard-coding, making command changes simpler.
To prevent loops in variable assignment, use :=
instead of =
:
CC := gcc
CC := ${CC}
Patterns and Functions
With patterns and functions, make
can automate more complex workflows. Here’s a makefile that compiles all .c
files in a directory:
.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}
SRCS := $(wildcard *.c)
stores all.c
files inSRCS
.BINS := $(SRCS:%.c=%)
convertsfoo.c
tofoo
, simplifying binary naming.- Each
%
pattern matches a target. For example,foo: foo.o
andfoo.o: foo.c
handle.o
files and binaries.
Example Workflow
Suppose the directory contains only foo.c
. The makefile expands as:
foo: foo.o
@echo "Checking..."
gcc -lm foo.o -o foo
foo.o: foo.c
@echo "Creating object..."
gcc -c foo.c
To clean up, make clean
will remove binaries and .o
files.
— — —
This blog is for educational purposes by Shaw Nguyen.