Skip to content

Make Cheatsheet

Overview

GNU Make is a build automation tool that automatically builds executable programs and libraries from source code by reading files called Makefiles which specify how to derive the target program.

Basic Concepts

Makefile Structure

makefile
target: dependencies
	command
	command

Key Terms

Target      # File to be created or action to be performed
Dependency  # Files that target depends on
Recipe      # Commands to create target
Rule        # Target + dependencies + recipe
Variable    # Named value that can be reused

Basic Makefile

Simple Example

makefile
# Simple C program build
program: main.o utils.o
	gcc -o program main.o utils.o

main.o: main.c
	gcc -c main.c

utils.o: utils.c
	gcc -c utils.c

clean:
	rm -f *.o program

With Variables

makefile
CC = gcc
CFLAGS = -Wall -g
OBJECTS = main.o utils.o
TARGET = program

$(TARGET): $(OBJECTS)
	$(CC) -o $(TARGET) $(OBJECTS)

main.o: main.c
	$(CC) $(CFLAGS) -c main.c

utils.o: utils.c
	$(CC) $(CFLAGS) -c utils.c

clean:
	rm -f $(OBJECTS) $(TARGET)

.PHONY: clean

Variables

Variable Definition

makefile
# Simple assignment
CC = gcc
CFLAGS = -Wall -g

# Recursive assignment (evaluated when used)
SOURCES = $(wildcard *.c)

# Simple assignment (evaluated immediately)
OBJECTS := $(SOURCES:.c=.o)

# Conditional assignment (only if not already defined)
CC ?= gcc

# Append to variable
CFLAGS += -O2

Built-in Variables

makefile
# Automatic variables
$@    # Target name
$<    # First dependency
$^    # All dependencies
$?    # Dependencies newer than target
$*    # Stem of pattern rule match

# Example usage
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

Environment Variables

makefile
# Use environment variables
HOME_DIR = $(HOME)
PATH_VAR = $(PATH)

# Override environment variables
export CC = gcc
export CFLAGS = -Wall

Pattern Rules

Basic Patterns

makefile
# Pattern rule for C files
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Pattern rule for C++ files
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

# Multiple patterns
%.o: %.c %.h
	$(CC) $(CFLAGS) -c $< -o $@

Advanced Patterns

makefile
# Static pattern rule
$(OBJECTS): %.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Pattern with subdirectories
build/%.o: src/%.c
	@mkdir -p build
	$(CC) $(CFLAGS) -c $< -o $@

Functions

String Functions

makefile
# Substitution
SOURCES = main.c utils.c
OBJECTS = $(SOURCES:.c=.o)
# or
OBJECTS = $(patsubst %.c,%.o,$(SOURCES))

# Word functions
FIRST_WORD = $(firstword $(SOURCES))
WORD_COUNT = $(words $(SOURCES))
NTH_WORD = $(word 2,$(SOURCES))

# Filter functions
C_FILES = $(filter %.c,$(SOURCES))
NOT_MAIN = $(filter-out main.c,$(SOURCES))

File Functions

makefile
# Wildcard
SOURCES = $(wildcard src/*.c)
HEADERS = $(wildcard include/*.h)

# Directory functions
SRC_DIRS = $(dir $(SOURCES))
BASE_NAMES = $(notdir $(SOURCES))
BASENAMES = $(basename $(SOURCES))
SUFFIXES = $(suffix $(SOURCES))

# Path manipulation
ABS_PATH = $(abspath relative/path)
REAL_PATH = $(realpath symlink/path)

Conditional Functions

makefile
# If function
RESULT = $(if $(DEBUG),debug,release)

# Or function
COMPILER = $(or $(CC),gcc)

# And function
VALID = $(and $(CC),$(SOURCES))

Conditional Statements

ifeq/ifneq

makefile
ifeq ($(DEBUG),1)
    CFLAGS += -g -DDEBUG
else
    CFLAGS += -O2 -DNDEBUG
endif

ifneq ($(OS),Windows_NT)
    CFLAGS += -fPIC
endif

ifdef/ifndef

makefile
ifdef DEBUG
    CFLAGS += -g
endif

ifndef CC
    CC = gcc
endif

Conditional Assignment

makefile
# Set if not already defined
CC ?= gcc

# Set based on condition
CFLAGS = $(if $(DEBUG),-g -O0,-O2)

Advanced Features

Include Files

makefile
# Include other makefiles
include config.mk
include $(wildcard *.d)

# Silent include (no error if file doesn't exist)
-include optional.mk

Dependency Generation

makefile
# Automatic dependency generation
DEPDIR := .deps
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d

%.o: %.c $(DEPDIR)/%.d | $(DEPDIR)
	$(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@

$(DEPDIR):
	@mkdir -p $@

DEPFILES := $(SOURCES:%.c=$(DEPDIR)/%.d)
$(DEPFILES):

include $(wildcard $(DEPFILES))

Order-Only Prerequisites

makefile
# Create directory before building objects
$(OBJECTS): | $(OBJDIR)

$(OBJDIR):
	mkdir -p $(OBJDIR)

Phony Targets

Common Phony Targets

makefile
.PHONY: all clean install test help

all: $(TARGET)

clean:
	rm -f $(OBJECTS) $(TARGET)

install: $(TARGET)
	cp $(TARGET) /usr/local/bin/

test: $(TARGET)
	./$(TARGET) --test

help:
	@echo "Available targets:"
	@echo "  all     - Build the program"
	@echo "  clean   - Remove build files"
	@echo "  install - Install the program"
	@echo "  test    - Run tests"

Multi-Target Builds

Multiple Programs

makefile
PROGRAMS = client server
COMMON_OBJS = utils.o network.o

all: $(PROGRAMS)

client: client.o $(COMMON_OBJS)
	$(CC) -o $@ $^

server: server.o $(COMMON_OBJS)
	$(CC) -o $@ $^

clean:
	rm -f *.o $(PROGRAMS)

.PHONY: all clean

Library Building

makefile
LIBNAME = libutils
STATIC_LIB = $(LIBNAME).a
SHARED_LIB = $(LIBNAME).so
LIB_OBJS = utils.o network.o

all: $(STATIC_LIB) $(SHARED_LIB)

$(STATIC_LIB): $(LIB_OBJS)
	ar rcs $@ $^

$(SHARED_LIB): $(LIB_OBJS)
	$(CC) -shared -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -fPIC -c $< -o $@

Command Line Usage

Basic Commands

bash
# Build default target
make

# Build specific target
make clean
make install

# Build with variables
make DEBUG=1
make CC=clang

# Parallel builds
make -j4
make -j$(nproc)

# Dry run (show commands without executing)
make -n

# Silent mode
make -s

# Keep going on errors
make -k

Debugging

bash
# Print variables
make -p

# Debug mode
make -d

# Print database
make --print-data-base

# Trace execution
make --trace

Complex Examples

C++ Project

makefile
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -g
LDFLAGS = -lpthread

SRCDIR = src
OBJDIR = obj
SOURCES = $(wildcard $(SRCDIR)/*.cpp)
OBJECTS = $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)
TARGET = myprogram

.PHONY: all clean

all: $(TARGET)

$(TARGET): $(OBJECTS)
	$(CXX) $(OBJECTS) -o $@ $(LDFLAGS)

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp | $(OBJDIR)
	$(CXX) $(CXXFLAGS) -c $< -o $@

$(OBJDIR):
	mkdir -p $(OBJDIR)

clean:
	rm -rf $(OBJDIR) $(TARGET)

# Dependency generation
-include $(OBJECTS:.o=.d)

$(OBJDIR)/%.d: $(SRCDIR)/%.cpp | $(OBJDIR)
	$(CXX) -MM -MT $(@:.d=.o) $< > $@

Multi-Directory Project

makefile
# Project structure
PROJECT_ROOT = .
SRC_DIRS = src src/utils src/network
INC_DIRS = include
OBJ_DIR = build
BIN_DIR = bin

# Find all source files
SOURCES = $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c))
OBJECTS = $(SOURCES:%.c=$(OBJ_DIR)/%.o)
TARGET = $(BIN_DIR)/myapp

# Compiler settings
CC = gcc
CFLAGS = -Wall -g $(addprefix -I,$(INC_DIRS))
LDFLAGS = -lm

.PHONY: all clean dirs

all: dirs $(TARGET)

dirs:
	@mkdir -p $(OBJ_DIR) $(BIN_DIR)
	@mkdir -p $(foreach dir,$(SRC_DIRS),$(OBJ_DIR)/$(dir))

$(TARGET): $(OBJECTS)
	$(CC) $^ -o $@ $(LDFLAGS)

$(OBJ_DIR)/%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -rf $(OBJ_DIR) $(BIN_DIR)

Best Practices

Makefile Organization

makefile
# Variables at the top
CC = gcc
CFLAGS = -Wall -g
TARGET = myprogram

# Default target first
all: $(TARGET)

# Pattern rules
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Specific rules
$(TARGET): $(OBJECTS)
	$(CC) $^ -o $@

# Phony targets at the end
.PHONY: all clean install

clean:
	rm -f *.o $(TARGET)

Error Handling

makefile
# Check for required tools
ifeq ($(shell which $(CC)),)
    $(error Compiler $(CC) not found)
endif

# Validate variables
ifndef SOURCES
    $(error SOURCES variable is not defined)
endif

# Conditional compilation
ifeq ($(shell pkg-config --exists gtk+-3.0; echo $$?),0)
    CFLAGS += $(shell pkg-config --cflags gtk+-3.0)
    LDFLAGS += $(shell pkg-config --libs gtk+-3.0)
else
    $(warning GTK+ 3.0 not found, building without GUI)
endif

Performance Tips

makefile
# Use order-only prerequisites for directories
$(OBJECTS): | $(OBJDIR)

# Minimize shell calls
SOURCES := $(shell find src -name '*.c')

# Use built-in rules when possible
# Instead of:
# %.o: %.c
#     $(CC) $(CFLAGS) -c $< -o $@
# Just use the built-in rule

# Parallel-safe directory creation
$(OBJDIR):
	@mkdir -p $@

Integration

With Version Control

makefile
# Get version from git
VERSION = $(shell git describe --tags --always --dirty)
CFLAGS += -DVERSION=\"$(VERSION)\"

# Include git info
GIT_COMMIT = $(shell git rev-parse HEAD)
BUILD_DATE = $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
CFLAGS += -DGIT_COMMIT=\"$(GIT_COMMIT)\" -DBUILD_DATE=\"$(BUILD_DATE)\"

With Package Managers

makefile
# pkg-config integration
CFLAGS += $(shell pkg-config --cflags libssl)
LDFLAGS += $(shell pkg-config --libs libssl)

# Check dependencies
check-deps:
	@pkg-config --exists libssl || (echo "libssl not found" && exit 1)

Troubleshooting

Common Issues

bash
# Missing separator error
# Make sure recipes use tabs, not spaces

# Target not found
make: *** No rule to make target 'foo'. Stop.
# Check target name and dependencies

# Circular dependency
make: Circular dependency dropped.
# Review dependency chain

# Debug makefile
make -p | grep target_name
make -d

Debugging Tips

makefile
# Print variable values
debug:
	@echo "CC = $(CC)"
	@echo "CFLAGS = $(CFLAGS)"
	@echo "SOURCES = $(SOURCES)"
	@echo "OBJECTS = $(OBJECTS)"

# Check if file exists
check-file:
	@test -f myfile.c && echo "File exists" || echo "File missing"

Resources