Understanding Makefiles: How They Work and Why You Need Them

Shaw Nguyen
3 min readNov 15, 2024

--

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 in SRCS.
  • BINS := $(SRCS:%.c=%) converts foo.c to foo, simplifying binary naming.
  • Each % pattern matches a target. For example, foo: foo.o and foo.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.

--

--

Shaw Nguyen
Shaw Nguyen

No responses yet