Variables and Automatic Variables
Lesson 3: Variables and Automatic Variables
Real Makefiles would be unreadable if you typed every compiler command in full. Variables let you define values once and reuse them โ and Make's automatic variables hand you the current rule's target and prerequisites for free.
Defining and Using Variables
CC = gcc
CFLAGS = -Wall -O2
TARGET = myapp
$(TARGET): main.o util.o
$(CC) $(CFLAGS) -o $(TARGET) main.o util.o
Reference a variable with $(NAME) (or ${NAME}). Make expands it wherever it appears โ in targets, prerequisites, and recipes.
The Four Assignment Operators
This is where newcomers get confused. The operator you choose changes when the value is expanded:
| Operator | Name | Behavior |
|---|---|---|
= | Recursive | Expanded every time it's used; can reference variables defined later. Flexible but can surprise you. |
:= | Simple | Expanded immediately at definition time. Predictable โ recommended for most cases. |
?= | Conditional | Set only if not already defined. Perfect for user-overridable defaults. |
+= | Append | Adds text to an existing variable. |
# Simple (:=) โ evaluated NOW
PREFIX := /usr/local
# Conditional (?=) โ only set if the user didn't pass one
PREFIX ?= /opt/app
# Append (+=) โ adds to whatever's there
CFLAGS += -g
:= by default for predictability. Use ?= for anything you want users to override on the command line (make PREFIX=/tmp/foo). Reach for = only when you genuinely need lazy evaluation.
Command-Line Overrides
Any variable can be overridden at invocation time, without editing the file:
$ make CFLAGS="-O0 -g" TARGET=debug-app
Command-line values beat file assignments (unless the file uses the override directive โ rare, and usually a smell). This is how packaging systems and CI pipelines customize one Makefile for many environments.
Automatic Variables: $@, {{LESSON_CONTENT}}lt;, $^, and Friends
Inside a recipe, Make provides automatic variables that describe the rule currently being executed. They're the secret to writing rules that work for any file:
| Variable | Meaning |
|---|---|
$@ | The target of the current rule. |
{{LESSON_CONTENT}}lt; | The first prerequisite. |
$^ | All prerequisites, deduplicated. |
$+ | All prerequisites, duplicates kept. |
$? | Prerequisites that are newer than the target โ the ones that triggered the rebuild. |
$* | The "stem" matched by a pattern rule (Lesson 5). |
# A rule that compiles any single .c file into a .o file
%.o: %.c
$(CC) $(CFLAGS) -c {{LESSON_CONTENT}}lt; -o $@
# A link rule: $^ expands to all objects
myapp: main.o util.o
$(CC) $(CFLAGS) -o $@ $^
When Make runs the pattern rule for util.o: $@ becomes util.o, {{LESSON_CONTENT}}lt; becomes util.c. One rule, any number of files.
Environment Variables and Make
Make automatically imports environment variables as Make variables. If your shell has CFLAGS exported, Make sees it โ though file assignments take precedence. This is handy but can cause "works on my machine" surprises; a common hardening step is:
unexport CFLAGS # ignore CFLAGS from the environment
Naming Conventions That Matter
Make has strong conventions for certain variables, and following them unlocks free features:
CCโ the C compiler (defaults tocc).CXXโ the C++ compiler.CFLAGS/CXXFLAGSโ compiler flags.LDFLAGS/LDLIBSโ linker flags and libraries.PREFIX,DESTDIRโ install locations.
make -p prints the entire default database โ every built-in variable and implicit rule. It's the best way to discover what Make knows out of the box.
๐ง Knowledge Check
1. What does the := assignment operator do?
2. In the rule %.o: %.c being executed for main.o, what does $@ expand to?
3. How do you override a Makefile variable without editing the file?