Make Build Tool

Make cheatsheet — write Makefiles, define targets, variables, phony rules. make build, make clean, make -j4, $@, $<, $^. Automate builds and tasks with GNU Make.

7 min read

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 make available.
  • 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 like VAR = value.
  • Phony Targets: Targets that do not represent actual files but rather actions or commands (e.g., clean, install). They are declared using .PHONY to 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 the Makefile. Example: If Makefile starts with all: program, running make will build program.

  • make target_name: Executes the specified target_name. Example: make clean will execute the commands for the clean target.

  • make -C directory: Change to directory before doing anything. Example: make -C src build will look for and execute the build target in the src directory’s Makefile.

  • make -f Makefile_name: Use Makefile_name instead of the default Makefile. Example: make -f GNUmakefile install will use GNUmakefile and execute the install target.

  • make variable=value: Set a variable at the command line, overriding any definition in the Makefile. Example: make CXXFLAGS="-O2 -g" will set the CXXFLAGS variable to "-O2 -g" for this run.

  • make -j number_of_jobs: Run jobs in parallel. number_of_jobs specifies the maximum number of commands to run simultaneously. Example: make -j 4 will 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., -lm for the math library).
  • RM = rm -f: Command to remove files.
  • PREFIX = /usr/local: Installation prefix.

Built-in Targets

  • .PHONY: target_name: Declares target_name as a phony target. Example:
    .PHONY: clean
    
    clean:
    	rm -f *.o myprogram
    

Automatic Variables

  • $@: The name of the target. Example: In myprogram: main.o utils.o, $@ refers to myprogram.

  • $^: The names of all prerequisites, with duplicates removed. Example: In myprogram: main.o utils.o, $^ refers to main.o utils.o.

  • $<: The name of the first prerequisite. Example: In myprogram: main.o utils.o, $< refers to main.o.

  • $?: The names of all prerequisites that are newer than the target. Example: If main.c is newer than main.o, and utils.c is older, $? would be main.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 .o file from a corresponding .c file.
  • $(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 .c to .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 Makefile is the default target if you run make without 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 clean in your directory, make clean might do nothing if Make thinks the clean file is up-to-date. Declaring targets like clean as .PHONY prevents this.
  • Parallel Execution (-j): While powerful, parallel execution can sometimes expose race conditions or dependencies that weren’t explicitly stated in the Makefile, 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.