Table of Contents

Makefile Debugging

Debugging Makefiles is often about understanding why Make made certain decisions. Use flags, echo commands, and systematic investigation.

Dry-run mode (-n): Preview what Make would do without executing:

make -n
make -n clean

Shows all recipes that would run, in order. Useful for verifying dependencies before running for real.

Verbose mode (-d): Print detailed debug info (heavy output):

make -d | grep -E "Considering|No need|Must remake" | head -20

Shows Make's internal reasoning: which files it's considering, which are out of date, etc. Produces lots of output.

Print variables: Use @echo to inspect values:

debug:
	@echo "SRCS: $(SRCS)"
	@echo "OBJS: $(OBJS)"
	@echo "CFLAGS: $(CFLAGS)"

Run with make debug. Helps verify that variables expand as expected.

Print rules: See what rules apply to a target:

make -p | grep -A 5 "main.o"

-p (print database) shows all targets and rules. Search for your target to see dependencies and recipe.

Debugging dependency issues: If a file isn't rebuilding when you expect:

make -d main.o 2>&1 | grep -E "newer|rebuild|remake"

Shows whether Make thinks main.o is up to date, and why. Look for timestamp comparisons.

Silent recipes: Recipes print commands by default. Suppress with @:

app: main.o
	gcc -o app main.o           # prints: gcc -o app main.o
 
quiet_app: main.o
	@gcc -o app main.o          # prints nothing, just the output (if any)

Use @ in recipes to reduce noise. But for debugging, remove @ to see what's executing.

Echo before execution: Use @echo then the command:

app: main.o
	@echo "Linking..."
	gcc -o app main.o

Prints Linking... then runs gcc. Useful for progress messages.

Conditional debugging:

ifdef VERBOSE
    Q =
else
    Q = @
endif
 
app: main.o
	$(Q)echo "Linking..."
	$(Q)gcc -o app main.o

Set Q to @ (silent) or empty (verbose). Run with make VERBOSE=1 for details.

Testing pattern rules: If pattern rules aren't matching:

make -n -p | grep "main.o"

Shows which rules could build main.o and their prerequisites. Helps diagnose why a pattern rule didn't apply.

Checking for circular dependencies:

make --trace 2>&1 | head -20

Shows the call stack. Circular dependencies often appear as loops in the trace.

Makefile syntax errors: Make's error messages can be cryptic:

Makefile:5: *** missing separator. Stop.

This means line 5 has a recipe without a tab. Check for spaces before commands.

Common mistakes:

# Wrong: space before recipe (not tab)
app: main.o
 gcc -o app main.o     # ERROR: missing separator
 
# Wrong: variable not defined
echo "$(UNDEFINED)"
 
# Wrong: circular dependency
a: b
b: a
 
# Correct: recipe with tab
app: main.o
	gcc -o app main.o

When stuck, use make -n to see what Make thinks should happen, and make -d for detailed reasoning.