0 - Make tutorial
Table of Contents
Section titled “Table of Contents”- What is Make?
- Installation
- Basic Concepts
- Your First Makefile
- Makefile Syntax
- Variables
- Pattern Rules
- Built-in Functions
- Common Use Cases
- Best Practices
- Troubleshooting
What is Make?
Section titled “What is Make?”Make is a build automation tool that automatically builds executable programs and libraries from source code by reading files called Makefiles. It determines which parts of a program need to be recompiled and issues commands to recompile them.
Key Benefits:
- Automatic dependency management
- Incremental builds (only rebuilds what changed)
- Cross-platform compatibility
- Simple yet powerful syntax
Installation
Section titled “Installation”Linux (Ubuntu/Debian)
Section titled “Linux (Ubuntu/Debian)”sudo apt updatesudo apt install build-essentialLinux (CentOS/RHEL/Fedora)
Section titled “Linux (CentOS/RHEL/Fedora)”# CentOS/RHELsudo yum groupinstall "Development Tools"
# Fedorasudo dnf groupinstall "Development Tools"# Install Xcode Command Line Toolsxcode-select --install
# Or using Homebrewbrew install makeWindows
Section titled “Windows”- Install through WSL (Windows Subsystem for Linux)
- Use MinGW-w64
- Install through Visual Studio Build Tools
Verify installation:
make --versionBasic Concepts
Section titled “Basic Concepts”Targets, Prerequisites, and Recipes
Section titled “Targets, Prerequisites, and Recipes”A Makefile consists of rules with this structure:
target: prerequisites recipe- Target: The file to be created or action to be performed
- Prerequisites: Files that the target depends on
- Recipe: Commands to create the target (must be indented with a TAB)
How Make Works
Section titled “How Make Works”- You run
make target - Make checks if the target file exists
- If prerequisites are newer than the target, or target doesn’t exist, Make runs the recipe
- Make recursively checks prerequisites
Your First Makefile
Section titled “Your First Makefile”Create a file named Makefile (no extension):
# Simple C program compilationprogram: main.c gcc -o program main.c
clean: rm -f program
.PHONY: cleanUsage:
make program # Compiles the programmake clean # Removes compiled filesmake # Runs the first target (program)Makefile Syntax
Section titled “Makefile Syntax”Comments
Section titled “Comments”# This is a commenttarget: prereq # Inline comment # Comment in recipe commandLine Continuation
Section titled “Line Continuation”long-command: gcc -Wall -Wextra -std=c99 \ -O2 -g -o program \ main.c utils.cMultiple Targets
Section titled “Multiple Targets”all: program1 program2 program3
program1: main1.c gcc -o program1 main1.c
program2: main2.c gcc -o program2 main2.c
program3: main3.c gcc -o program3 main3.cVariables
Section titled “Variables”Defining Variables
Section titled “Defining Variables”# Simple assignmentCC = gccCFLAGS = -Wall -Wextra -std=c99TARGET = myprogramSOURCES = main.c utils.c math.c
# Recursive assignment (evaluated when used)OBJECTS = $(SOURCES:.c=.o)
# Immediate assignment (evaluated when defined)DATE := $(shell date)Using Variables
Section titled “Using Variables”$(TARGET): $(OBJECTS) $(CC) $(CFLAGS) -o $(TARGET) $(OBJECTS)
%.o: %.c $(CC) $(CFLAGS) -c $< -o $@Automatic Variables
Section titled “Automatic Variables”# $@ - Target name# $< - First prerequisite# $^ - All prerequisites# $? - Prerequisites newer than target
program: main.o utils.o gcc -o $@ $^ # Same as: gcc -o program main.o utils.o
%.o: %.c gcc -c $< -o $@ # Same as: gcc -c main.c -o main.oEnvironment Variables
Section titled “Environment Variables”# Use environment variablesprogram: echo "User: $(USER)" echo "Path: $(PATH)"
# Override environment variablesPATH = /custom/pathPattern Rules
Section titled “Pattern Rules”Implicit Rules
Section titled “Implicit Rules”Make has built-in rules for common operations:
# These are automatic:%.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
# Just specify dependenciesprogram: main.o utils.o $(CC) -o $@ $^# Must write separate rule for each file!main.o: main.c gcc -c main.c -o main.o
utils.o: utils.c gcc -c utils.c -o utils.o
math.o: math.c gcc -c math.c -o math.o
# The several commands above can replace with this one%.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<Custom Pattern Rules
Section titled “Custom Pattern Rules”# Compile .cpp files to .o files%.o: %.cpp $(CXX) $(CXXFLAGS) -c $< -o $@
# Convert .md to .html%.html: %.md pandoc $< -o $@
# Multiple pattern matching%_test: %_test.o %.o $(CC) -o $@ $^Built-in Functions
Section titled “Built-in Functions”String Functions
Section titled “String Functions”SOURCES = main.c utils.c math.c
# SubstitutionOBJECTS = $(subst .c,.o,$(SOURCES))# orOBJECTS = $(SOURCES:.c=.o)
# Pattern substitutionOBJECTS = $(patsubst %.c,%.o,$(SOURCES))
# Word functionsFIRST_SOURCE = $(firstword $(SOURCES))WORD_COUNT = $(words $(SOURCES))File Functions
Section titled “File Functions”# Wildcard expansionC_FILES = $(wildcard *.c)HEADERS = $(wildcard include/*.h)
# Directory functionsSRC_DIR = srcSOURCES = $(wildcard $(SRC_DIR)/*.c)OBJECTS = $(patsubst $(SRC_DIR)/%.c,obj/%.o,$(SOURCES))
# Create directories# must evaluate obj if not exists# Order-Only Prerequisites (|)# The | symbol means "order-only prerequisite":$(OBJECTS): | objobj: mkdir -p objControl Functions
Section titled “Control Functions”# Conditional assignmentDEBUG ?= 0
# If-elseCFLAGS = -Wallifeq ($(DEBUG),1) CFLAGS += -g -DDEBUGelse CFLAGS += -O2 -DNDEBUGendif
# Function callsSUPPORTED_OS = linux darwinifeq ($(filter $(OS),$(SUPPORTED_OS)),) $(error Unsupported OS: $(OS))endifCommon Use Cases
Section titled “Common Use Cases”C/C++ Project
Section titled “C/C++ Project”- Don’t forget to create
srcdirectory which contains*.csource files
# Project settingsCC = gccCXX = g++CFLAGS = -Wall -Wextra -std=c99CXXFLAGS = -Wall -Wextra -std=c++17TARGET = myprojectSRC_DIR = srcOBJ_DIR = objBIN_DIR = bin
# Find source filesC_SOURCES = $(shell find $(SRC_DIR) -name "*.c")CXX_SOURCES = $(shell find $(SRC_DIR) -name "*.cpp")C_OBJECTS = $(C_SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)CXX_OBJECTS = $(CXX_SOURCES:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o)OBJECTS = $(C_OBJECTS) $(CXX_OBJECTS)
# Default targetall: $(BIN_DIR)/$(TARGET)
# Link$(BIN_DIR)/$(TARGET): $(OBJECTS) | $(BIN_DIR) $(CXX) -o $@ $^
# Compile C files$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) $(CC) $(CFLAGS) -c $< -o $@
# Compile C++ files$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR) $(CXX) $(CXXFLAGS) -c $< -o $@
# Create directories$(OBJ_DIR) $(BIN_DIR): mkdir -p $@
# Cleanclean: rm -rf $(OBJ_DIR) $(BIN_DIR)
# Installinstall: $(BIN_DIR)/$(TARGET) cp $< /usr/local/bin/
# Runrun: $(BIN_DIR)/$(TARGET) ./$(BIN_DIR)/$(TARGET)
.PHONY: all clean install runDocumentation Generation
Section titled “Documentation Generation”DOCS_DIR = docsMD_FILES = $(wildcard $(DOCS_DIR)/*.md)HTML_FILES = $(MD_FILES:%.md=%.html)
docs: $(HTML_FILES)
%.html: %.md pandoc $< -o $@
clean-docs: rm -f $(DOCS_DIR)/*.html
.PHONY: docs clean-docsTesting
Section titled “Testing”TEST_DIR = testsTEST_SOURCES = $(wildcard $(TEST_DIR)/*_test.c)TEST_TARGETS = $(TEST_SOURCES:%.c=%)
tests: $(TEST_TARGETS) @for test in $(TEST_TARGETS); do \ echo "Running $$test..."; \ ./$$test || exit 1; \ done
%_test: %_test.c $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean-tests: rm -f $(TEST_TARGETS)
.PHONY: tests clean-testsBest Practices
Section titled “Best Practices”1. Use .PHONY for Non-File Targets
Section titled “1. Use .PHONY for Non-File Targets”.PHONY: all clean install test- if not use .PHONY, this might confuse Make.
# Without .PHONYclean: rm -f *.o program
test: ./program --test
install: cp program /usr/local/bin/- What if someone creates actual files named clean, test, or install?
# Someone accidentally creates these files:touch cleantouch testtouch install
# Now Make thinks these targets are up-to-date!make clean# Output: make: 'clean' is up to date.# The rm command never runs!2. Set Default Variables
Section titled “2. Set Default Variables”CC ?= gccCFLAGS ?= -Wall -O2PREFIX ?= /usr/local3. Create Output Directories
Section titled “3. Create Output Directories”$(OBJECTS): | $(OBJ_DIR)$(OBJ_DIR): mkdir -p $(OBJ_DIR)4. Handle Dependencies
Section titled “4. Handle Dependencies”# Generate dependency filesDEPDIR = .depsDEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(DEPDIR)/%.d | $(DEPDIR) $(OBJ_DIR) $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@
$(DEPDIR): mkdir -p $(DEPDIR)
DEPFILES := $(C_SOURCES:$(SRC_DIR)/%.c=$(DEPDIR)/%.d)$(DEPFILES):
include $(wildcard $(DEPFILES))5. Use Consistent Naming
Section titled “5. Use Consistent Naming”# Good naming conventionSRC_DIR = srcOBJ_DIR = objBIN_DIR = binTEST_DIR = testsINCLUDE_DIR = include6. Add Help Target
Section titled “6. Add Help Target”help: @echo "Available targets:" @echo " all - Build the project" @echo " clean - Remove build files" @echo " install - Install the program" @echo " test - Run tests" @echo " help - Show this help"
.PHONY: helpTroubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”1. “missing separator” error
- Make sure recipes are indented with TAB, not spaces
- Check for mixed tabs and spaces
2. “No rule to make target” error
- Check spelling of target names
- Ensure prerequisites exist or can be built
- Verify file paths
3. “Nothing to be done for target”
- Target file is newer than prerequisites
- Use
make -Bto force rebuild - Check file timestamps
4. Variables not expanding
# Wrong - recursive assignment creates infinite loopCFLAGS = $(CFLAGS) -Wall
# Right - use appendCFLAGS += -Wall
# Or use immediate assignmentCFLAGS := $(CFLAGS) -WallDebug Tips
Section titled “Debug Tips”1. Print variable values:
debug: @echo "CC: $(CC)" @echo "CFLAGS: $(CFLAGS)" @echo "SOURCES: $(SOURCES)" @echo "OBJECTS: $(OBJECTS)"
.PHONY: debug2. Use dry run:
make -n target # Show commands without executingmake -p # Print database of rules and variables3. Enable debugging:
make -d target # Debug modemake --trace # Trace executionMakefile Templates
Section titled “Makefile Templates”Save time with this basic template:
# Project ConfigurationPROJECT = myprojectCC = gccCFLAGS = -Wall -Wextra -std=c99 -O2SRC_DIR = srcOBJ_DIR = objBIN_DIR = bin
# Find SourcesSOURCES = $(wildcard $(SRC_DIR)/*.c)OBJECTS = $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)TARGET = $(BIN_DIR)/$(PROJECT)
# Default Targetall: $(TARGET)
# Build Target$(TARGET): $(OBJECTS) | $(BIN_DIR) $(CC) -o $@ $^
# Compile Objects$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) $(CC) $(CFLAGS) -c $< -o $@
# Create Directories$(OBJ_DIR) $(BIN_DIR): mkdir -p $@
# Cleanclean: rm -rf $(OBJ_DIR) $(BIN_DIR)
# Runrun: $(BIN_DIR)/$(TARGET) ./$(BIN_DIR)/$(TARGET)
# Helphelp: @echo "Targets: all, clean, help"
.PHONY: all clean helpCompiler flags
Section titled “Compiler flags”-Wall - Warning All
// This code has problems that -Wall will catch:#include <stdio.h>
int main() { int x; // WARNING: unused variable int y = 5; // WARNING: variable set but not used printf("%d\n"); // WARNING: format expects argument but none given return 0;}-Wextra - Extra Warnings
// Additional warnings that -Wextra catches:void function(int param) { // WARNING: unused parameter printf("Hello\n");}
int main() { int a = 5; unsigned int b = 3;
if (a > b) { // WARNING: signed/unsigned comparison printf("a is bigger\n"); }
return 0;}-std=c99 - C Standard
// C99 features that this enables:#include <stdio.h>#include <stdbool.h> // C99: bool type
int main() { // C99: Variable declarations anywhere (not just at top) for (int i = 0; i < 10; i++) { // C99: declare i in for loop bool found = true; // C99: bool type // C99: Single-line comments with // printf("Number: %d\n", i); }
return 0;}-O2 - Optimization Level 2
// Code that benefits from -O2 optimization:int main() { int sum = 0;
// Without optimization: loop runs 1000 times // With -O2: compiler might optimize this entirely for (int i = 0; i < 1000; i++) { sum += 1; }
printf("Sum: %d\n", sum); // Compiler knows sum = 1000 return 0;}Conclusion
Section titled “Conclusion”Make is a powerful build automation tool that can significantly streamline your development workflow. Start with simple Makefiles and gradually incorporate more advanced features as your projects grow in complexity.
Next Steps:
- Practice with small C/C++ projects
- Explore GNU Make documentation for advanced features
- Look into alternative build systems (CMake, Ninja) for complex projects
- Integrate Make with your IDE or editor