What it is
Make is a build automation tool that reads a file named Makefile and runs commands to build, compile, and manage software projects. You reach for Make when you need to automate repetitive tasks in a project, especially compilation and linking.
Installation
Linux
sudo apt update && sudo apt install make # Debian/Ubuntu
sudo yum install make # Fedora/CentOS/RHEL
sudo dnf install make # Newer Fedora
macOS
Make is typically pre-installed on macOS. If not, you can install it via Homebrew:
brew install make
Windows
You can install Make on Windows through various methods:
- Git for Windows: Includes a Git Bash terminal that has
makeavailable. - Chocolatey:
choco install make - MSYS2: Install MSYS2 and then use its package manager (
pacman):pacman -S make
Core Concepts
- Targets: The specific files or actions you want Make to produce (e.g.,
program,clean,install). Targets are usually listed at the beginning of a rule. - Prerequisites (Dependencies): Files or other targets that must exist or be up-to-date before a target can be built. If any prerequisite is newer than the target, Make will rebuild the target.
- Recipes (Commands): The shell commands that Make executes to create the target from its prerequisites. Recipes must be indented with a literal tab character.
- Variables: Similar to shell variables, they store strings that can be reused throughout the
Makefile. They are defined likeVAR = value. - Phony Targets: Targets that do not represent actual files but rather actions or commands (e.g.,
clean,install). They are declared using.PHONYto prevent Make from getting confused if a file with the same name exists.
Commands / Usage
A Makefile typically contains rules. The make command itself is used to invoke these rules.
Basic Rule Structure
target: prerequisite1 prerequisite2
command1
command2
Common make Invocation
-
make: Executes the first target defined in theMakefile. Example: IfMakefilestarts withall: program, runningmakewill buildprogram. -
make target_name: Executes the specifiedtarget_name. Example:make cleanwill execute the commands for thecleantarget. -
make -C directory: Change todirectorybefore doing anything. Example:make -C src buildwill look for and execute thebuildtarget in thesrcdirectory’sMakefile. -
make -f Makefile_name: UseMakefile_nameinstead of the defaultMakefile. Example:make -f GNUmakefile installwill useGNUmakefileand execute theinstalltarget. -
make variable=value: Set a variable at the command line, overriding any definition in theMakefile. Example:make CXXFLAGS="-O2 -g"will set theCXXFLAGSvariable to"-O2 -g"for this run. -
make -j number_of_jobs: Run jobs in parallel.number_of_jobsspecifies the maximum number of commands to run simultaneously. Example:make -j 4will use up to 4 parallel jobs.make -j(without a number) will use as many jobs as possible. -
make --version: Display the Make version. Example:make --version -
make --help: Display a help message. Example:make --help
Common Makefile Directives and Variables
Variables
CC = gcc: Default C compiler.CXX = g++: Default C++ compiler.CFLAGS = -Wall -g: Flags for the C compiler.CXXFLAGS = -Wall -g -std=c++11: Flags for the C++ compiler.LDFLAGS =: Flags for the linker.LDLIBS = -lm: Libraries to link against (e.g.,-lmfor the math library).RM = rm -f: Command to remove files.PREFIX = /usr/local: Installation prefix.
Built-in Targets
.PHONY: target_name: Declarestarget_nameas a phony target. Example:.PHONY: clean clean: rm -f *.o myprogram
Automatic Variables
-
$@: The name of the target. Example: Inmyprogram: main.o utils.o,$@refers tomyprogram. -
$^: The names of all prerequisites, with duplicates removed. Example: Inmyprogram: main.o utils.o,$^refers tomain.o utils.o. -
$<: The name of the first prerequisite. Example: Inmyprogram: main.o utils.o,$<refers tomain.o. -
$?: The names of all prerequisites that are newer than the target. Example: Ifmain.cis newer thanmain.o, andutils.cis older,$?would bemain.o.
Common Targets
-
all: Usually the default target, builds the main program or all primary outputs. Example:all: myprogram myprogram: main.o utils.o $(CC) main.o utils.o -o $@ $(LDLIBS) -
clean: Removes generated files (object files, executables). Example:clean: $(RM) *.o myprogram -
install: Copies the built program and related files to their installation locations. Example:install: myprogram cp myprogram $(PREFIX)/bin/ -
uninstall: Removes installed files. Example:uninstall: rm -f $(PREFIX)/bin/myprogram -
test: Runs tests for the project. Example:test: myprogram ./run_tests.sh -
dist: Creates a distribution archive (e.g., a tarball). Example:DISTNAME = myproject-1.0 dist: tar czf $(DISTNAME).tar.gz --exclude='.git' --exclude='Makefile' .
Common Patterns
Compiling a Single File
myprogram: main.c
$(CC) main.c -o $@
Explanation: Compiles main.c into an executable named myprogram. $@ is the target name (myprogram).
Compiling Multiple Source Files into an Executable
TARGET = myprogram
SRCS = main.c utils.c helper.c
OBJS = $(SRCS:.c=.o) # Replaces .c suffix with .o
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@ $(LDLIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
Explanation: This is a very common pattern.
$(OBJS) = $(SRCS:.c=.o): Creates a list of object files (main.o,utils.o,helper.o) from the source files.$(TARGET): $(OBJS): The target executable depends on all object files.%.o: %.c: A pattern rule. It says how to make any.ofile from a corresponding.cfile.$(CC) $(CFLAGS) -c $< -o $@: Compiles a single source file ($<is the first prerequisite, e.g.,main.c) into an object file ($@is the target, e.g.,main.o) without linking (-c).
Cleaning Up Generated Files
.PHONY: clean
clean:
rm -f *.o $(TARGET)
Explanation: A standard target to remove all object files and the final executable.
Installing a Program
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
.PHONY: install
install: $(TARGET)
mkdir -p $(BINDIR)
cp $(TARGET) $(BINDIR)/
Explanation: Copies the compiled $(TARGET) to the specified binary directory. mkdir -p ensures the directory exists.
Compiling with C++
CXX = g++
CXXFLAGS = -Wall -g -std=c++11
TARGET = myapp
SRCS = main.cpp utils.cpp
OBJS = $(SRCS:.cpp=.o)
$(TARGET): $(OBJS)
$(CXX) $(OBJS) -o $@ $(LDLIBS)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
Explanation: Similar to C, but uses g++ and CXXFLAGS. The pattern rule %.o: %.cpp handles C++ source files.
Using a Makefile in a Subdirectory
# Top-level Makefile
SUBDIRS = src include docs
all:
for dir in $(SUBDIRS); do $(MAKE) -C $$dir all; done
clean:
for dir in $(SUBDIRS); do $(MAKE) -C $$dir clean; done
.PHONY: all clean
Explanation: The top-level Makefile iterates through subdirectories and calls make all or make clean within each. $$dir is used because $ is special in Makefiles; $$ escapes it so the shell sees $dir.
Gotchas
- Tab Indentation: Recipes (commands) in Makefiles must be indented with a literal tab character, not spaces. This is a common source of errors.
- Implicit Rules: Make has built-in rules for common tasks (like compiling
.cto.o). You can override or disable these. Understanding them can be helpful but also lead to unexpected behavior if not managed. - Order of Targets: The first target in a
Makefileis the default target if you runmakewithout arguments. - Variable Expansion: Make has different ways to expand variables (
=,:=,?=,+=).=performs lazy expansion (like a shell variable, expanded when used).:=performs immediate expansion (expanded when the variable is defined).?=assigns only if the variable is not already defined.+=appends to the variable.
- Phony Targets and Existing Files: If you have a file named
cleanin your directory,make cleanmight do nothing if Make thinks thecleanfile is up-to-date. Declaring targets likecleanas.PHONYprevents this. - Parallel Execution (
-j): While powerful, parallel execution can sometimes expose race conditions or dependencies that weren’t explicitly stated in theMakefile, leading to subtle build failures. Ensure all dependencies are correctly listed. - Special Characters: Shell metacharacters (like
*,?,!,$) within recipes need careful handling, often requiring quoting or escaping.$@,$^, etc., are Make variables and must be escaped ($$@) if you intend them to be literal characters in a shell command.