|

Make 01: Fundamentals – Targets, Prerequisites, and Recipes

← Previous GNU Make (1/4) Next →

Summary: Learn what GNU Make is, how it works, and how to write your first Makefile. By the end you will understand targets, prerequisites, recipes, variables, automatic variables, and .PHONY targets – enough to read and write basic Makefiles for any project.

KeyValue
OSUbuntu 24.04 LTS
Make versionGNU Make 4.3
Working directory~/projects/makefile-tutorial

0. Prerequisites

  • A Linux system (this tutorial uses Ubuntu 24.04 LTS)
  • A terminal and a text editor

Install GNU Make if it is not already present.

sudo apt update && sudo apt install -y make


Verify the installation.

make --version


GNU Make 4.3
Built for x86_64-pc-linux-gnu


Code language: CSS (css)

1. What Is Make?

Make is a build automation tool. It reads a file called Makefile and executes shell commands based on rules you define. Each rule says: “to build this target, check if these prerequisites have changed, and if so, run these recipe commands.”

The core idea is timestamps. If a target file is older than its prerequisites – or does not exist – Make runs the recipe to rebuild it. If the target is already up to date, Make skips it. This avoids repeating work that has already been done.

Make was originally designed for compiling C programs, but it works with any language or task that produces files from other files.


2. Your First Makefile

Create a working directory.

mkdir -p ~/projects/makefile-tutorial
cd ~/projects/makefile-tutorial


Code language: JavaScript (javascript)

Create a file called Makefile with this content.

hello:
	echo "Hello, Make!"


Code language: PHP (php)

Warning: The indented line must use a tab character, not spaces. Make requires tabs for recipe lines. If you use spaces, you will see the error *** missing separator. Stop.

Run it.

make


echo "Hello, Make!"
Hello, Make!


Code language: PHP (php)

Make prints each command before executing it. The first line is Make showing you what it will run. The second line is the actual output.

To suppress the command echo, prefix the recipe line with @.

hello:
	@echo "Hello, Make!"


Code language: CSS (css)

Now make only prints the output.

Hello, Make!



3. Anatomy of a Rule

Every Makefile rule has three parts.

target: prerequisites
	recipe


PartMeaning
TargetThe file to build, or a label to run
PrerequisitesFiles the target depends on – if any are newer than the target, the recipe runs
RecipeShell commands that build the target – each line must start with a tab

A rule can have all three parts, or just a target and a recipe.

Here is a rule with all three parts.

report.txt: data.txt
	wc -l data.txt > report.txt


Code language: CSS (css)

This says: “to build report.txt, check that data.txt exists and is newer. If so, count its lines and write the count to report.txt.”

Create data.txt and test it.

echo -e "line one\nline two\nline three" > data.txt
make report.txt


Code language: CSS (css)
wc -l data.txt > report.txt


Code language: CSS (css)

Check the result.

cat report.txt


Code language: CSS (css)
3 data.txt


Code language: CSS (css)

Run make report.txt again without changing data.txt.

make report.txt


Code language: CSS (css)
make: 'report.txt' is up to date.


Code language: HTTP (http)

Make skipped the recipe because report.txt already exists and is newer than data.txt. This is the timestamp logic at work.


4. Multiple Rules and the Default Target

A Makefile can contain many rules. When you type make with no arguments, Make runs the first target in the file. By convention this target is called all.

all: greeting.txt farewell.txt

greeting.txt:
	echo "Hello" > greeting.txt

farewell.txt:
	echo "Goodbye" > farewell.txt

clean:
	rm -f greeting.txt farewell.txt


Code language: CSS (css)

Run make and it builds all, which depends on both text files.

make


echo "Hello" > greeting.txt
echo "Goodbye" > farewell.txt


Code language: PHP (php)

The all target has no recipe of its own – it only lists prerequisites. Make builds both files because all depends on them.

Run make clean to remove the generated files.

make clean


rm -f greeting.txt farewell.txt


Code language: CSS (css)

5. Variables

Variables reduce repetition. Define them at the top of the Makefile and reference them with $(NAME).

OUTPUT_DIR = build
FILES = $(OUTPUT_DIR)/greeting.txt $(OUTPUT_DIR)/farewell.txt

all: $(FILES)

$(OUTPUT_DIR)/greeting.txt:
	mkdir -p $(OUTPUT_DIR)
	echo "Hello" > $(OUTPUT_DIR)/greeting.txt

$(OUTPUT_DIR)/farewell.txt:
	mkdir -p $(OUTPUT_DIR)
	echo "Goodbye" > $(OUTPUT_DIR)/farewell.txt

clean:
	rm -rf $(OUTPUT_DIR)


Code language: JavaScript (javascript)

There are four ways to assign a variable.

SyntaxMeaning
VAR = valueRecursively expanded – re-evaluated every time $(VAR) is used
VAR := valueSimply expanded – evaluated once at assignment time
VAR ?= valueConditional – set only if VAR is not already defined
VAR += valueAppend – adds to the existing value of VAR

The difference between = and := matters when variables reference other variables.

A = $(B)
B = hello
# $(A) expands to "hello" because = is lazy - it waits until A is used


Code language: PHP (php)
A := $(B)
B = hello
# $(A) expands to "" because := evaluated A immediately, before B was defined


Code language: PHP (php)

Tip: Use := when the value comes from a shell command or computation. Use = for simple string values.


6. Automatic Variables

Make provides special variables inside recipes that refer to parts of the current rule.

VariableMeaning
$@The target name
$<The first prerequisite
$^All prerequisites, space-separated
$?Only the prerequisites that are newer than the target

These save you from repeating filenames. Here is an example.

combined.txt: part1.txt part2.txt
	cat $^ > $@


$^ expands to part1.txt part2.txt (all prerequisites) and $@ expands to combined.txt (the target). This is identical to writing the following.

combined.txt: part1.txt part2.txt
	cat part1.txt part2.txt > combined.txt


Code language: CSS (css)

Test it.

echo "Part one" > part1.txt
echo "Part two" > part2.txt
make combined.txt


Code language: PHP (php)
cat part1.txt part2.txt > combined.txt


Code language: CSS (css)
cat combined.txt


Code language: CSS (css)
Part one
Part two



7. .PHONY Targets

Some targets are not files. They are labels for actions like clean, all, or test. If a file with the same name happens to exist in your directory, Make will think the target is already up to date and skip the recipe.

Declare non-file targets as .PHONY to prevent this.

.PHONY: all clean

all: greeting.txt farewell.txt

greeting.txt:
	echo "Hello" > greeting.txt

farewell.txt:
	echo "Goodbye" > farewell.txt

clean:
	rm -f greeting.txt farewell.txt


Code language: CSS (css)

Now make clean always runs, even if a file called clean exists in the directory.

Tip: Always mark action targets like all, clean, test, and install as .PHONY.


8. Comments and Recipe Prefixes

Comments start with #.

# Build both output files
all: greeting.txt farewell.txt  # this is an inline comment


Code language: PHP (php)

Recipe lines support two special prefixes.

PrefixEffect
@Suppress command echo – Make does not print the command before running it
-Ignore errors – Make continues even if the command fails
clean:
	@echo "Cleaning up..."
	-rm -f *.txt
	@echo "Done."


Code language: PHP (php)

The - before rm means Make will not stop if there are no .txt files to delete.


9. Putting It All Together

Here is a complete Makefile that combines everything from this tutorial.

# Variables
SRC_DIR  = src
BUILD_DIR = build
FILES    = $(BUILD_DIR)/report.txt $(BUILD_DIR)/summary.txt

# Default target
.PHONY: clean

all: $(FILES)

# Rule: build report from raw data
$(BUILD_DIR)/report.txt: $(SRC_DIR)/data.txt
	mkdir -p $(BUILD_DIR)
	wc -l $< > $@

# Rule: build summary from report
$(BUILD_DIR)/summary.txt: $(BUILD_DIR)/report.txt
	echo "Summary generated on $$(date)" > $@
	cat $< >> $@

clean:
	rm -rf $(BUILD_DIR)


Code language: PHP (php)

Set up the source file and run it.

mkdir -p src
echo -e "alpha\nbeta\ngamma\ndelta" > src/data.txt
make


Code language: PHP (php)
mkdir -p build
wc -l src/data.txt > build/report.txt
echo "Summary generated on $(date)" > build/summary.txt
cat build/report.txt >> build/summary.txt


Code language: PHP (php)

Note: $$(date) uses a double dollar sign to pass a literal $ to the shell. A single $(date) would be interpreted as a Make variable, which is probably empty.

Run make again without changing anything.

make


make: Nothing to be done for 'all'.


Code language: HTTP (http)

Make detected that every target is already up to date and skipped all the recipes.


Summary

You learned the core concepts of GNU Make.

  • A Makefile contains rules that tell Make how to build targets from prerequisites
  • Make uses file timestamps to decide what needs rebuilding and skips everything else
  • Variables ($(VAR)) reduce repetition across rules
  • Automatic variables ($@, $<, $^) reference parts of the current rule without repeating filenames
  • .PHONY declares targets that are actions, not files
  • Recipe lines must use tab characters, not spaces
  • @ suppresses command echo and - ignores errors

The next tutorial covers Make’s most common use case – compiling C and C++ projects, where the timestamp-based rebuild logic truly shines.

Similar Posts

Leave a Reply