Ir al contenido

GNU Make Cheatsheet

GNU Make Cheatsheet

Installation

PlatformCommand
Ubuntu/Debiansudo apt-get install build-essential or sudo apt-get install make
RHEL/CentOS/Fedorasudo yum groupinstall "Development Tools" or sudo dnf install make
Arch Linuxsudo pacman -S base-devel or sudo pacman -S make
Alpine Linuxapk add make
macOS (Xcode)xcode-select --install
macOS (Homebrew)brew install make (installs as gmake)
Windows (Chocolatey)choco install make
Windows (WSL)sudo apt-get install build-essential
Verificationmake --version

Basic Commands

CommandDescription
makeBuild the first target in Makefile (usually ‘all’)
make target_nameBuild a specific target (e.g., make clean, make install)
make cleanRemove generated files (must be defined in Makefile)
make -nDry run - show commands without executing them
make -f custom.mkUse a specific makefile instead of default
make -C /path/to/dirChange to directory before executing
make CC=clangSet/override a variable from command line
make CFLAGS="-O2 -Wall"Override compilation flags
make -j4Run 4 jobs in parallel for faster builds
make -jUse all available CPU cores for parallel builds
make -sSilent mode - don’t print commands being executed
make -iIgnore errors and continue building
make -kKeep going after errors, skip failed targets
make -BUnconditionally rebuild all targets
make -tTouch files to update timestamps without rebuilding
make -q targetCheck if target needs rebuilding (exit code 0=no, 1=yes)

Advanced Commands

CommandDescription
make -dShow detailed debug information during execution
make --debug=basicShow basic debug information (less verbose)
make -pPrint all rules and variable values
make -p -f /dev/nullShow all built-in rules and variables
make --warn-undefined-variablesWarn when undefined variables are referenced
make -j8 --load-average=4Parallel builds with system load limiting
make -OSynchronize output for parallel builds
make --output-sync=targetGroup output by target in parallel builds
make --traceShow why each target is being remade
make -eEnvironment variables override Makefile variables
make -rDisable built-in implicit rules
make -RDisable built-in variables
make -LCheck symlink times instead of target times
make DEBUG=1 VERBOSE=1Set multiple variables from command line
make -wPrint working directory before and after execution

Makefile Syntax

Basic Structure

# Variable definitions
CC = gcc
CFLAGS = -Wall -O2
SOURCES = main.c utils.c
OBJECTS = $(SOURCES:.c=.o)

# Target: prerequisites
target: prerequisite1 prerequisite2
	command1    # Must use TAB, not spaces
	command2

# Phony targets (not actual files)
.PHONY: all clean install

all: program

program: $(OBJECTS)
	$(CC) $(CFLAGS) -o $@ $^

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

clean:
	rm -f $(OBJECTS) program

Automatic Variables

VariableDescription
$@Target name
$<First prerequisite
$^All prerequisites (space-separated)
$?Prerequisites newer than target
$*Stem of pattern match
$(@D)Directory part of target
$(@F)File part of target
$(<D)Directory part of first prerequisite
$(<F)File part of first prerequisite

Built-in Functions

# Wildcard expansion
SOURCES = $(wildcard src/*.c)

# Pattern substitution
OBJECTS = $(patsubst %.c,%.o,$(SOURCES))
OBJECTS = $(SOURCES:.c=.o)  # Shorthand

# Directory/file operations
DIRS = $(dir $(SOURCES))
FILES = $(notdir $(SOURCES))
BASENAME = $(basename $(SOURCES))

# Shell execution
GIT_VERSION = $(shell git describe --tags)
FILE_COUNT = $(shell ls -1 *.c | wc -l)

# Conditional
BUILD_TYPE = $(if $(DEBUG),debug,release)

# Foreach loop
DIRS = src lib bin
ALL_SOURCES = $(foreach dir,$(DIRS),$(wildcard $(dir)/*.c))

# Filter operations
C_FILES = $(filter %.c,$(SOURCES))
NOT_TEST = $(filter-out %_test.c,$(SOURCES))

# String operations
UPPERCASE = $(shell echo $(PROJECT) | tr a-z A-Z)
JOINED = $(subst $(space),$(comma),$(LIST))

Conditional Directives

# ifdef/ifndef
ifdef DEBUG
CFLAGS += -g -DDEBUG
else
CFLAGS += -O2
endif

# ifeq/ifneq
ifeq ($(CC),gcc)
CFLAGS += -fno-strict-aliasing
endif

ifneq ($(PLATFORM),windows)
LDFLAGS += -lpthread
endif

# Comparing strings
ifeq ($(strip $(VERBOSE)),1)
Q =
else
Q = @
endif

Pattern Rules

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

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

# Static pattern rules
OBJECTS = foo.o bar.o baz.o
$(OBJECTS): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

# With different directories
$(BUILDDIR)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) -c $< -o $@

Configuration

Standard Makefile Variables

# Compilers
CC = gcc                    # C compiler
CXX = g++                   # C++ compiler
AS = as                     # Assembler
AR = ar                     # Archive tool
LD = ld                     # Linker

# Compiler flags
CFLAGS = -Wall -O2         # C compilation flags
CXXFLAGS = -Wall -O2       # C++ compilation flags
CPPFLAGS = -I./include     # Preprocessor flags
LDFLAGS = -L./lib          # Linker flags
LDLIBS = -lm -lpthread     # Libraries to link

# Installation directories
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
LIBDIR = $(PREFIX)/lib
INCLUDEDIR = $(PREFIX)/include

# Project structure
SRCDIR = src
BUILDDIR = build
OBJDIR = $(BUILDDIR)/obj
BINDIR_LOCAL = bin

Multi-file Project Configuration

# Project metadata
PROJECT = myapp
VERSION = 1.0.0
DESCRIPTION = My Application

# Source discovery
SOURCES = $(wildcard $(SRCDIR)/*.c)
HEADERS = $(wildcard $(SRCDIR)/*.h)
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))

# Dependencies
DEPS = $(OBJECTS:.o=.d)

# Include dependency files
-include $(DEPS)

# Automatic dependency generation
$(OBJDIR)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $(CPPFLAGS) -MMD -MP -c $< -o $@

# Target-specific variables
debug: CFLAGS += -g -DDEBUG -O0
debug: $(PROJECT)

release: CFLAGS += -O3 -DNDEBUG
release: $(PROJECT)

Including External Configuration

# Main Makefile
-include config.mk          # Optional local config
-include $(HOME)/.makerc    # User-specific settings
include rules.mk            # Required shared rules

# Export variables to sub-makes
export CC CXX CFLAGS LDFLAGS

config.mk Example

# Local machine-specific configuration
# This file is not committed to version control

# Override compiler
CC = clang
CXX = clang++

# Add sanitizers for development
CFLAGS += -fsanitize=address -fsanitize=undefined
LDFLAGS += -fsanitize=address -fsanitize=undefined

# Custom installation prefix
PREFIX = /opt/myapp

# Enable verbose output
VERBOSE = 1

Common Use Cases

Use Case 1: C/C++ Project Build System

# Makefile for C project with separate source/build directories
PROJECT = myprogram
CC = gcc
CFLAGS = -Wall -Wextra -std=c11 -O2
LDFLAGS = -lm

SRCDIR = src
BUILDDIR = build
BINDIR = bin

SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SOURCES))
DEPS = $(OBJECTS:.o=.d)

.PHONY: all clean install

all: $(BINDIR)/$(PROJECT)

$(BINDIR)/$(PROJECT): $(OBJECTS)
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
	@echo "Build complete: $@"

$(BUILDDIR)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) -MMD -MP -c $< -o $@

clean:
	rm -rf $(BUILDDIR) $(BINDIR)

install: $(BINDIR)/$(PROJECT)
	install -m 0755 $< /usr/local/bin/

-include $(DEPS)
# Build commands
make                    # Build project
make clean             # Remove build artifacts
make install           # Install to system
make -j4               # Parallel build with 4 jobs

Use Case 2: Multi-Target Build with Debug/Release

PROJECT = myapp
SRCDIR = src
BUILDDIR_DEBUG = build/debug
BUILDDIR_RELEASE = build/release

SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS_DEBUG = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR_DEBUG)/%.o,$(SOURCES))
OBJECTS_RELEASE = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR_RELEASE)/%.o,$(SOURCES))

CC = gcc
CFLAGS_COMMON = -Wall -Wextra -std=c11
CFLAGS_DEBUG = $(CFLAGS_COMMON) -g -O0 -DDEBUG
CFLAGS_RELEASE = $(CFLAGS_COMMON) -O3 -DNDEBUG

.PHONY: all debug release clean

all: debug release

debug: $(BUILDDIR_DEBUG)/$(PROJECT)

release: $(BUILDDIR_RELEASE)/$(PROJECT)

$(BUILDDIR_DEBUG)/$(PROJECT): $(OBJECTS_DEBUG)
	@mkdir -p $(dir $@)
	$(CC) $^ -o $@

$(BUILDDIR_RELEASE)/$(PROJECT): $(OBJECTS_RELEASE)
	@mkdir -p $(dir $@)
	$(CC) $^ -o $@

$(BUILDDIR_DEBUG)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS_DEBUG) -c $< -o $@

$(BUILDDIR_RELEASE)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS_RELEASE) -c $< -o $@

clean:
	rm -rf build
# Usage
make debug             # Build debug version
make release           # Build release version
make all               # Build both versions

Use Case 3: Testing and Code Quality

PROJECT = myapp
TEST_DIR = tests
TEST_SOURCES = $(wildcard $(TEST_DIR)/*_test.c)
TEST_BINS = $(patsubst $(TEST_DIR)/%.c,$(TEST_DIR)/bin/%,$(TEST_SOURCES))

CC = gcc
CFLAGS = -Wall -Wextra -std=c11 -I./src
LDFLAGS = -lcheck -lm

.PHONY: all test coverage lint clean

all: $(PROJECT)

test: $(TEST_BINS)
	@echo "Running tests..."
	@for test in $(TEST_BINS); do \
		echo "Running $$test..."; \
		$$test || exit 1; \
	done
	@echo "All tests passed!"

$(TEST_DIR)/bin/%: $(TEST_DIR)/%.c src/*.c
	@mkdir -p $(dir $@)
	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

coverage: CFLAGS += --coverage
coverage: LDFLAGS += --coverage
coverage: clean test
	gcov src/*.c
	lcov --capture --directory . --output-file coverage.info
	genhtml coverage.info --output-directory coverage_html

lint:
	cppcheck --enable=all --suppress=missingIncludeSystem src/
	clang-tidy src/*.c -- $(CFLAGS)

clean:
	rm -rf $(TEST_DIR)/bin *.gcda *.gcno *.gcov coverage.info coverage_html
# Usage
make test              # Run all tests
make coverage          # Generate coverage report
make lint              # Run static analysis

Use Case 4: Documentation Generation

PROJECT = myapp
DOC_DIR = docs
BUILD_DOC_DIR = $(DOC_DIR)/build

SOURCES = $(wildcard src/*.c src/*.h)
MD_FILES = $(wildcard $(DOC_DIR)/*.md)

.PHONY: docs docs-html docs-pdf clean-docs

docs: docs-html docs-pdf

docs-html: $(BUILD_DOC_DIR)/html/index.html

docs-pdf: $(BUILD_DOC_DIR)/$(PROJECT).pdf

# Generate HTML documentation with Doxygen
$(BUILD_DOC_DIR)/html/index.html: $(SOURCES) Doxyfile
	@mkdir -p $(BUILD_DOC_DIR)
	doxygen Doxyfile

# Generate PDF from Markdown
$(BUILD_DOC_DIR)/$(PROJECT).pdf: $(MD_FILES)
	@mkdir -p $(BUILD_DOC_DIR)
	pandoc $(MD_FILES) -o $@ \
		--toc \
		--number-sections \
		-V geometry:margin=1in

# Generate man pages
$(BUILD_DOC_DIR)/man/$(PROJECT).1: $(DOC_DIR)/$(PROJECT).1.md
	@mkdir -p $(dir $@)
	pandoc $< -s -t man -o $@

clean-docs:
	rm -rf $(BUILD_DOC_DIR)
# Usage
make docs              # Generate all documentation
make docs-html         # Generate HTML docs only
make docs-pdf          # Generate PDF docs only

Use Case 5: Asset Processing Pipeline

SRC_IMG_DIR = assets/images
DIST_IMG_DIR = dist/images
SRC_CSS_DIR = assets/css
DIST_CSS_DIR = dist/css
SRC_JS_DIR = assets/js
DIST_JS_DIR = dist/js

PNG_SOURCES = $(wildcard $(SRC_IMG_DIR)/*.png)
PNG_OPTIMIZED = $(patsubst $(SRC_IMG_DIR)/%.png,$(DIST_IMG_DIR)/%.png,$(PNG_SOURCES))

SCSS_SOURCES = $(wildcard $(SRC_CSS_DIR)/*.scss)
CSS_FILES = $(patsubst $(SRC_CSS_DIR)/%.scss,$(DIST_CSS_DIR)/%.css,$(SCSS_SOURCES))

JS_SOURCES = $(wildcard $(SRC_JS_DIR)/*.js)
JS_MINIFIED = $(patsubst $(SRC_JS_DIR)/%.js,$(DIST_JS_DIR)/%.min.js,$(JS_SOURCES))

.PHONY: all assets images css js clean

all: assets

assets: images css js

images: $(PNG_OPTIMIZED)

css: $(CSS_FILES)

js: $(JS_MINIFIED)

# Optimize PNG images
$(DIST_IMG_DIR)/%.png: $(SRC_IMG_DIR)/%.png
	@mkdir -p $(dir $@)
	optipng -o7 $< -out $@

# Compile SCSS to CSS
$(DIST_CSS_DIR)/%.css: $(SRC_CSS_DIR)/%.scss
	@mkdir -p $(dir $@)
	sass --style=compressed $< $@

# Minify JavaScript
$(DIST_JS_DIR)/%.min.js: $(SRC_JS_DIR)/%.js
	@mkdir -p $(dir $@)
	uglifyjs $< -c -m -o $@

clean:
	rm -rf dist
# Usage
make assets            # Process all assets
make images            # Optimize images only
make css               # Compile SCSS only
make js                # Minify JavaScript only
make -j4 assets        # Process in parallel

Best Practices

  • Use .PHONY targets: Declare targets that don’t create files as .PHONY to avoid conflicts with actual files and improve performance

    .PHONY: all clean install test
  • Generate dependencies automatically: Use compiler flags like -MMD -MP to automatically track header file dependencies, preventing incomplete rebuilds

    DEPS = $(OBJECTS:.o=.d)
    -include $(DEPS)
    $(CC) $(CFLAGS) -MMD -MP -c $< -o $@
  • Create directories automatically: Use @mkdir -p $(dir $@) before creating output files to ensure target directories exist

    $(BUILDDIR)/%.o: $(SRCDIR)/%.c
        @mkdir -p $(dir $@)
        $(CC) -c $< -o $@
  • Use variables for flexibility: Define compiler, flags, and paths as variables at the top of your Makefile for easy customization and maintenance

    CC = gcc
    CFLAGS = -Wall -O2
    PREFIX = /usr/local
  • Leverage parallel builds: Use make -j for faster builds on multi-core systems, especially for large projects with independent compilation units

  • Separate source and build directories: Keep generated files in a separate build/ directory to maintain clean source trees and simplify .gitignore management

  • Use pattern rules over explicit rules: Pattern rules like %.o: %.c are more maintainable and scalable than writing individual rules for each file

  • Provide informative output: Use @echo to provide build progress messages while suppressing command echoing with @ for cleaner output

    $(TARGET): $(OBJECTS)
        @echo "Linking $@..."
        @$(CC) $^ -o $@

Troubleshooting

IssueSolution
”missing separator” errorEnsure recipe lines are indented with TAB characters, not spaces. Configure your editor to insert tabs in Makefiles
Target always rebuildsCheck file timestamps with ls -l. Use make -d to debug why Make thinks rebuild is needed. Verify prerequisites exist and have correct timestamps
Variables are emptyUse make -p to print all variables and their values. Check for typos in variable names. Verify variable assignment syntax (= vs := vs ?=)
Parallel build failuresSome targets may have missing dependencies. Run make -j1 to identify the issue, then add proper prerequisites. Use order-only prerequisites (`
”No rule to make target” errorVerify the target name is spelled correctly. Check that prerequisite files exist. Use make -p to see all available targets
Commands not foundEnsure required tools are installed and in PATH. Use full paths for commands or check with which command. Prefix with @ to suppress output and see actual errors
Recursive make issuesUse $(MAKE) instead of make in recipes. Export necessary variables with export. Consider using -C to change directories instead of recursive makes
Pattern rules not matchingVerify pattern syntax (% placement). Check that source files exist in expected locations. Use make -d to see rule matching process
Changes not detectedEnsure file modification times are correct. Use make -B to force rebuild. Check if using -t touched files incorrectly. Verify dependencies are listed correctly