Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Comprehensive Practical Guide to Linux Makefile Usage with Step-by-Step Examples

Tech 1

Makefile Core Structure

Each Makefile consists of a set of build rules, following this standard format:

<targets>... : <prerequisites>...
<tab><commands>
  • Targets: Can be the name of an output file to generate, or a label for an action (e.g. clean for file removal).
  • Prerequisites: Files or other targets that must exist or be updated before the target can be built.
  • Commands: Shell operations executed to generate the target from its prerequisites. All command lines must be prefixed with a single tab character, not spaces.

Sample minimal Makefile for a C++ program:

CXX = g++
app: main.cc
	$(CXX) -o $@ $^

.PHONY: clean
clean:
	rm -f app

Note: On Linux, C++ source files may use .cc, .cpp, or .cxx extensions interchangeably. The g++ compiler handles C++ syntax, while gcc is used for C code.

Makefile Execution Workflow

  1. Dependency Validation: Before building a target, make first checks if all prerequisites exist. If any prerequisite is missing, make searches for a rule to generate that missing file first.
  2. Freshness Check: If all prerequisites exist, make compares the modification timestamp of each prerequisite against the target. If any prerequisite has a newer timestamp than the target, the target is marked for rebuild.
  3. Command Execution: For targets marked for update, make runs the associated command sequence as defined in the rule.

Common make Command Options

Default make execution

Running make without arguments executes the first target defined in the Makefile. For example, given the following Makefile:

client: client.cc
	g++ -o client client.cc
server: server.cc
	g++ -o server server.cc

Only the client target will be built when running make with no arguments.

make clean

The clean target is conventionally used to delete compiled object files, binaries, and other build artifacts.

make -f

Use this option to specify a non-default Makefile name. For example, to use a build definition named production_build.mk, run:

make -f production_build.mk
  • When using -f, make will not search for the default Makefile, makefile, or GNUmakefile files unless explicitly specified.
  • Multiple -f flags can be passed to combine rules from multiple Makefiles. If duplicate rules are defined across files, later definitions override earlier ones. This is useful for splitting build configurations across different environments or feature sets.

make -C

Changes the working directory to the specified path before loading the Makefile. For example, given this project structure:

ecommerce_platform/
├── Makefile
└── services/
    ├── Makefile
    ├── payment/
    └── inventory/

To run the Makefile in the services directory from the project root, execute:

make -C services

make -n

Prints all commands that would be executed for a target, without actually running them. This is useful for debugging build rules without modifying the filesystem.

make -s

Runs build commands in silent mode, suppressing command echo to standard output.

Makefile Variables

Variables reduce repetition in build rules, and are referenced using the $(VARIABLE_NAME) syntax.

Variable Assignment Operators

  • = (Recursive Expansion): Variable values are evaluated lazily, meaning the value is resolved every time the variable is referenced, not at assignment time.
  • := (Simple Expansion): Variable values are evaluated immediately at assignment time, and remain fixed unless explicitly modified later. Use the override directive to prevent command-line variable assignments from overriding values defined in the Makefile.

Custom Variables

You can define custom variables to store compiler paths, build flags, and other repeated values:

CXX = g++
BUILD_FLAGS = -Wall -O2
app: main.cc
	$(CXX) $(BUILD_FLAGS) -o app main.cc

Automatic Variables

Make provides built-in automatic variables that reference values from the current rule:

  • $@: The target name of the current rule
  • $<: The first prerequisite in the current rule
  • $^: All prerequisites in the current rule, with duplicate entries removed

Sample 1: Single source file build

app: main.cc
	g++ -o $@ $<

Equivalent to:

app: main.cc
	g++ -o app main.cc

Sample 2: Multi-object link step

app: io.o utils.o main.o
	g++ -o $@ $^

Equivalent to:

app: io.o utils.o main.o
	g++ -o app io.o utils.o main.o

Pattern Matching with %

The % wildcard defines generic pattern rules that apply to all files matching a given suffix. For example, this rule compiles any C++ source file to a corresponding object file:

CXX = g++
CXXFLAGS = -Wall -c
%.o: %.cc
	$(CXX) $(CXXFLAGS) $< -o $@

.PHONY Directive

.PHONY declares pseudo-targets, which are labels for actions rather than actual output files. Marking a target as phony tells make to skip checking for a file with the target name, and always run the associated commands when the target is called, even if a file with that name exists in the working directory.

The all target is a conventional pseudo-target that lists all top-level build outputs as prerequisites. Running make without arguments will build all dependencies of the all target if it is the first rule defined.

Sample pseudo-target declaration:

.PHONY: all deploy clean

all: client server

deploy:
	scp client server user@production-host:/opt/app/

clean:
	rm -f client server *.o

To run the pseudo-targets, call:

make all && make deploy && make clean

Sample Clean Rule

This clean rule recursively removes all object files from the current directory and subdirectories:

.PHONY: clean
clean:
	rm -f $(shell find ./ -name "*.o") client server

Sample Multi-Binary Build Rule

This Makefile builds two separate executables from their respective source files:

all: client server

client: client.cc network.cc
	g++ -o $@ $^

server: server.cc network.cc
	g++ -o $@ $^

clean:
	rm -f client server *.o

.PHONY: all clean

Tip: Chain commands with semicolons to run a clean followed by a full build in one line:

make clean; make

Tip: Prefix commands with @ to suppress command echo. This is useful for multi-step administrative tasks:

.PHONY: build_all
build_all:
	@cd ./auth-service; make; cd ../
	@cd ./order-service; make; cd ../

Common Built-in Functions

wildcard

The wildcard function returns a list of files in the current directory matching the specified pattern:

# Get all .cc files in the current working directory
SRCS = $(wildcard *.cc)

To recursively find files in subdirectories, use the shell function with find:

# Find all .cc files in the ./src directory and all its subdirectories
SRCS := $(shell find ./src -name "*.cc")

patsubst

The patsubst (pattern substitution) function replaces substrings matching a pattern in a given text string:

# List of source files
SOURCES = auth.cc payment.cc common.h
# Replace all .cc extensions with .o to get object file names
OBJECTS = $(patsubst %.cc, %.o, $(SOURCES))

Practical Makefile Samples

Single Source File Build

This Makefile builds an executable named app from main.cc:

app: main.cc
	g++ -o $@ $^

.PHONY: clean
clean:
	rm -f app

Run make to build the executable, and make clean to remove it.

Multi-Binary Build

This Makefile builds two separate executables for client and server:

all: client server

client: client.cc
	g++ -o $@ $^

server: server.cc
	g++ -o $@ $^

clean:
	rm -f client server *.o

.PHONY: all clean

Note: The .PHONY declaration can be placed anywhere in the Makefile.

Tags: Linux

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.