| name | Makefile Debugging and Optimization |
| description | This skill should be used when the user asks to debug a Makefile, diagnose slow builds, fix unexpected rebuild behavior, understand why make rebuilds everything, figure out why a target does not rebuild, check Makefile variable values, use make -n/-d/-p/--trace flags, optimize build performance, or troubleshoot any Makefile issue. Also applies when the user reports "build is slow", "everything rebuilds", or "target never rebuilds". |
| version | 1.0.0 |
Makefile Debugging and Optimization
Overview
Teach systematic debugging first using Make's built-in diagnostic flags. When users report issues (slow builds, incorrect behavior, mysterious rebuilds), start with debugging methodology, not immediate solutions. Make's debugging flags reveal the root cause - use them proactively.
Core Anti-Pattern to Avoid
❌ Jump to Solutions Without Diagnosis
User: "My build is slow"
Agent: "Here are 8 optimizations to try:
1. Use ccache
2. Enable parallel builds
3. Optimize compiler flags
..."
Problems:
- Guessing: No diagnosis of actual problem
- Shotgun approach: Try many things, hope one works
- Misses root cause: May not address actual issue
- Not educational: User doesn't learn debugging approach
- Wastes time: Implementing irrelevant fixes
Even if suggestions are good, this misses teaching opportunity.
Correct Pattern: Systematic Debugging
✅ Debug First, Then Fix
User: "My build is slow"
Agent: "Let's debug systematically to find the root cause.
First, let's see what Make is actually doing:
1. Dry-run to see planned actions:
make -n all
2. Check what's being rebuilt and why:
make -d all 2>&1 | grep 'File.*is newer'
3. Time different scenarios:
make clean && time make all # Full build
touch file.c && time make all # Incremental
This will show us exactly what's slow. Then we can fix the specific issue.
[After seeing output:]
The debug output shows Make is rebuilding everything because...
Here's the specific fix..."
Benefits:
- Accurate diagnosis: See actual problem
- Educational: User learns debugging approach
- Targeted fix: Address root cause
- Reproducible: User can debug future issues
- Professional: This is how experts debug
Make's Essential Debugging Flags
Always Suggest These First
make -n (Dry-Run)
Purpose: Show what would be executed without running commands
make -n all
make -n install
make all && make -n all
When to use:
- "What will this Makefile do?"
- "Why is Make rebuilding?"
- Verify Makefile logic before running
make -d (Debug Mode)
Purpose: Show Make's decision-making process
make -d all 2>&1 | less
make -d all 2>&1 | grep "Considering\|File.*is newer"
make -d mytarget 2>&1 | grep mytarget
When to use:
- "Why did Make rebuild this?"
- "How is Make resolving dependencies?"
- "Why is Make not rebuilding when it should?"
make -p (Print Database)
Purpose: Show all rules, variables, and values
make -p | less
make -p | grep "^CC ="
make -p | grep "^%"
make -p | grep "automatic"
When to use:
- "What's the value of this variable?"
- "What implicit rules exist?"
- "Why is this variable not what I expect?"
make --trace (GNU Make 4.0+)
Purpose: Show rule executions in real-time
make --trace all
touch file.c
make --trace all
When to use:
- "What's the execution order?"
- "What triggered this rebuild?"
- More readable than
-d for basic tracing
Quick Reference Card
| Flag | Purpose | Common Use |
|---|
-n | Dry-run (what would run) | "Will this work?" |
-d | Debug (why decisions made) | "Why rebuilt?" |
-p | Database (all rules/vars) | "What's the value?" |
--trace | Trace execution | "What ran and why?" |
Always mention these when user has Makefile problems.
Systematic Debugging Approach
Step 1: Understand What's Happening
make -n target
make -p | grep VARIABLE_NAME
make --question target; echo $?
Step 2: Identify the Problem
time make clean && time make all
touch one_file.c && time make all
make all
make -n all
touch dependency.h
make -d target 2>&1 | grep dependency.h
Step 3: Find Root Cause
make -d target.o 2>&1 | grep -A5 "target.o"
make --trace all
make -p | grep "^VARNAME\s*="
Step 4: Implement Targeted Fix
Now suggest specific solutions based on diagnosis, not guesses.
Common Issues and How to Debug Them
Issue: "Build is Slow"
Start by measuring: time make clean && time make all for baseline, touch src/main.c && time make all for incremental. Identify whether compilation or linking is slow before suggesting optimizations.
Issue: "Everything Rebuilds"
Verify with make all && make -n all (should say "nothing to be done"). If it wants to rebuild, use make -d all 2>&1 | grep 'File.*is newer' to find the timestamp causing it.
Issue: "Target Doesn't Rebuild When It Should"
Check known dependencies with make -p | grep -A10 "^target:". Then touch dependency.h && make -d target 2>&1 | grep dependency.h to verify Make sees the file change.
When to Suggest Optimizations
After Understanding the Problem
Timing showed slow compilation:
→ Suggest ccache, -j flag, optimize flags
Timing showed slow linking:
→ Suggest split debug info, reduce -O level for dev
Debug showed all files rebuild:
→ Fix timestamp issue, check .d file generation
Debug showed no parallelism:
→ Suggest .NOTPARALLEL removal, add -j flag
Debug showed expensive wildcard:
→ Cache results, use immediate expansion :=
Proactive Debugging Education
When User Reports Issue
Template response structure:
- Acknowledge issue
- Suggest debugging approach (flags to use)
- Wait for or request output
- Analyze output
- Provide targeted fix
Example:
"That rebuild behavior sounds incorrect. Let's debug it systematically:
make all
make -n all
make -d all 2>&1 | grep 'File.*is newer'
Can you run these and share the output? That will show us exactly what's causing unnecessary rebuilds.
[After seeing output:]
Ah, I see the issue. Make thinks config.h is newer than the targets because... Here's how to fix it..."
When Creating Makefiles
Proactively include debugging support:
.PHONY: debug-make help-debug
debug-make:
@echo "==> Dry-run (what would be executed):"
@$(MAKE) -n all
@echo ""
@echo "==> Variable values:"
@$(MAKE) -p | grep "^CC\|^CFLAGS\|^LDFLAGS"
help-debug:
@echo "Debugging targets:"
@echo " make -n target - Show what would run"
@echo " make -d target - Show decision-making"
@echo " make -p | grep VAR - Show variable value"
@echo " make --trace - Trace execution"
Mention in help target:
help: ## Show this help
@echo "Debugging:"
@echo " make -n all - Dry-run to see planned commands"
@echo " make -d all - Debug why Make is rebuilding"
@echo " make debug-make - Show Makefile diagnostics"
Common Debugging Scenarios
Step-by-step debugging procedures for mysterious rebuilds, targets that never rebuild, wrong variable values, and slow incremental builds are in references/debugging-scenarios.md.
Optimization Principles
Measure Before Optimizing
time make clean && time make all
touch src/main.c
time make all
make all
stat target dependencies
Common Optimizations (After Diagnosis)
1. Parallel Builds (if compilation is slow)
MAKEFLAGS += -j$(shell nproc)
2. Compiler Cache (if recompiling same files)
CC := ccache gcc
3. Optimize Dev Flags (if dev build is slow)
CFLAGS := -g -Og
ifdef SANITIZE
CFLAGS += -fsanitize=address
endif
4. Lazy Wildcard (if startup is slow)
SRCS := $(wildcard src/*.c)
SRCS = $(wildcard src/*.c)
5. Conditional Dependency Inclusion
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
The Bottom Line
Don't guess - debug. Make has powerful diagnostic flags that reveal exactly what's happening. Use them first:
- Problem reported → Suggest debugging flags
- Run flags, see output → Understand root cause
- Root cause identified → Provide targeted fix
- User learns → Can debug future issues themselves
Teaching debugging methodology is more valuable than providing quick fixes. Users who learn make -n, make -d, and make -p can solve their own problems.
Always frame debugging as first step, not afterthought.
Quick Diagnostic Commands
See references/debugging-scenarios.md for a copy-paste-ready cheat sheet of diagnostic commands.