You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The make command is a build automation tool primarily used for
compiling and building projects in C, C++, and other programming languages.
It simplifies the process of compiling code, linking libraries, and creating
executables by reading instructions from a configuration file called a
Makefile.
Additionally, it checks the return value of each executed command in the
Makefile. If a command returns an error (a non-zero value), make
will exit.
Notes: Only lines starting with tabs are treated as commands.
Using the space key instead of the tab key will result in the
errormakefile:x: *** missing separator. Stop.
Makefile
A Makefile consists of a set of rules, each specifying how to compile
files, generate targets, or perform other tasks related to building the
project.
These rules may include shell commands as well as custom commands
defined within the Makefile syntax.
A rule consists of three parts: target, dependency set (prerequisites)
and command set.
The dependency set of a rule can be empty.
Basic Syntax of Makefile
Code
# If `target1 = target2`, the second rule `target2: dependency set2`# overwrites the first rule `target1: dependency set1`.target1: dependency set1
command set1
target2: dependency set2
command set2
...;
# Even though `target1 = target2`, both rules are preserved and executed sequentially:# First, the first rule; Then, the second rule.# First, `build dependency set1` and execute `command set1`;# Second, `build dependency set2` and execute `command set2`;target1:: dependency set1
command set1
target2:: dependency set2
command set2
...;
targetset: dependency set
command set
...;
# The following commented code is equivalent to the one above.# target1: dependency set# command set# target2: dependency set# command set# ...;
Explanation
target:
It is a file you want to create, except for a pseudo target.
It can be an object file, a .cpp file, a .txt file, and so on.
It also can be a set of target.
dependency:
It is a file that a target requires during its creation, except
for a pseudo target used as a dependency.
It can be an object file, a .cpp file, a .txt file, and so on.
Typically, it's unnecessary to explicitly list header files because the
compiler can automatically track dependencies between source files and
header files.
command set:
It is a set of commands used to compile all dependency files into a target
file.
Notes:
Only files listed as targets or within the dependency set will be checked
for existence or modifications.
How make Processes a Makefile
Prerequisites
Simply type the make command without any suffix or option.
Ensure all source files and the Makefile are in the same folder.
Steps
Check for a Makefile in the current folder:
If the Makefile does not exist, exit and report an error: "No targets
specified and no makefile found. Stop.".
If the Makefile is found, proceed to the next step.
Identify the first target:
Locate the first target in the Makefile and treat it as the final or
default target.
For example, in the basic rule of the Makefile above, target1 is the
first target.
Build the dependency graph:
make examines the dependencies listed in the Makefile and constructs a
dependency graph.
Each target can depend on other files or targets, which are updated
recursively if necessary.
Each node in the dependency graph represents a target or a file with or
without an associated command set.
A child node represents a dependency of its parent (a file or target).
The root node represents the final target and its command set.
The command set is not limited to generating its associated target file; it
can generate multiple files.
The make command does not dynamically update the dependency graph.
Once executed, the dependency graph is constructed and remains static until
another make command is invoked.
Therefore, files generated during the make process should be specified in
the Makefile.
Recursively scan the dependency graph:
Traverse the dependency graph in a breadth-first manner, starting from the
leaves up to the root.
While scanning the tree, check if the file corresponding to the current
node exists or if its timestamp is newer than the root's.
If the file exists and its timestamp is not newer than the root’s,
continue scanning.
If the file’s timestamp is newer than the root’s, execute the parent
node's commands to rebuild it.
If the file does not exist and it's not a leaf, execute its command set
and rebuild it.
If the file does not exist and it's a leaf with a command set, execute
its command set and rebuild it.
If the file does not exist and it's a leaf without a command set, exit
and report an error: "No rule to make target 'xxx', needed by 'yyy'.
Stop.".
Notes
To understand the process of make, manually build a dependency tree
starting from the root.
.PHONY, a Pseudo Target
Syntax
.PHONY: a_pseudo_target1, a_pseudo_target2, ...;
Explanation and Usage
A pseudo-target (also called a phony target) in a Makefile is a target
that does not represent a real file.
It is used to group commands or perform actions, such as cleaning up
files or running non-file related tasks like tests.
Since these targets do not correspond to actual files, they are marked as
.PHONY to avoid conflicts with files of the same name that might exist in
the directory.
Notes
Not use pseudo-targets to generate files whenever possible, as the commands
associated with pseudo-targets are executed every time the make command
runs.
Not include a pseudo-target in a dependency set whenever possible, as the
command set corresponding to the dependency set with the pseudo-target is
always executed every time the make command runs.
Pseudo-targets are treated as if they are updated on every execution of
make.
Example
.PHONY: clean
clean:
rm -f *.o *.exe
Implicit Rule
Explanation
In a Makefile, an implicit rule is a predefined or general rule that
tells make how to build certain types of files automatically without
explicitly specifying a rule for each file.
These rules are built into make and handle common tasks like compiling
.c or .cpp files into object files, linking object files into
executables, and more.
Common Implicit Rules
Compiling .c to .o:
Implicit rule: n.o: n.c
Command: $(CC) -c $(CFLAGS) n.c -o n.o
This rule tells make how to compile a .c file into an object (.o) file.
Compiling .cpp to .o:
Implicit rule: n.o: n.cpp
Command: $(CXX) -c $(CXXFLAGS) n.cpp -o n.o
Similarly, this rule tells make how to compile C++ files into object files.
Linking object files into an executable:
Implicit rule: prog: prog.o
Command: $(CC) prog.o $(LDFLAGS) -o prog
This rule links an object file into an executable.
Notes
Avoid using implicit rules, as they can lead to various issues. Instead,
define your own rules to override them.
Use the command make -r or add .SUFFIXES: to your Makefile to prevent the
use of implicit rules.
Variables in Makefile
Explanation and Usage
In a Makefile, variables are used to store values (such as flags, paths,
or file lists) that can be referenced multiple times, simplifying the
Makefile's maintenance.
They are used in targets, dependency sets, and command sets.
A variable can represent multiple targets in a pattern rule or a list of
target files, allowing the pattern rule to apply to those targets.
However, there is an important limitation: a variable cannot be used directly
in place of the % in the target pattern.
The Syntax of Defining a Variable
Simple assignment (VAR_NAME = value):
The value is expanded when the variable is used.
Whenever its dependencies are updated, or anything that depends on it is
modified, all related components will be updated.
Immediate assignment (VAR_NAME := value):
The value is expanded when the variable is defined.
Whenever its dependencies are updated, or anything that depends on it is
modified, all related components will not be updated.
The environment variables and arguments passed by make cannot update it.
Conditional assignment (VAR_NAME ?= value):
Only assigns if the variable is not already defined.
Whenever its dependencies are updated, or anything that depends on it is
modified, all related components will be updated.
The Syntax of Referencing a Variable
$(VAR_NAME) # For Makefile simple variables to ensure clarity and compatibility.
${VAR_NAME} # Only when interacting with complex expressions or shell commands or tools that require it.
$VAR_NAME # Only for automatic variables like `$@`, `$<`, or `$^`.
Default Variables, Common Variables, Automatic Variables, Special Symbols and Environment Variables
Explanation
Default variables:
In Makefiles, default variables are pre-defined by make itself.
They come with default values and they can be overridden in your
Makefile if necessary.
Common variables:
In Makefiles, common variables refer to variables that are frequently
used in build scripts, such as CC for the compiler or CFLAGS for
compiler options.
These are often user-defined or customized for specific projects.
Automatic variables:
In Makefile, automatic variables are predefined variables that hold
specific values based on the rule being executed.
These variables are automatically set by make and allow you to write more
flexible and generalized rules.
They represent parts of a rule, such as the target, prerequisites
(prerequisities = dependencies), or the stem (The part of the file name
that % matches is called the stem.) of a filename in pattern rules.
Special symbols:
In Makefiles, special symbols help define rules and control execution.
Environment variables:
In a Makefile, environment variables play an important role in controlling
the build process.
These variables are inherited from the shell's environment and can be
used in the Makefile to set values for compilers, paths, flags, or other
settings.
Name of Default Variables
CC: Defaults to cc.
CXX: Defaults to g++.
MAKE: Defaults to make.
AR: Defaults to ar.
RM: Defaults to rm -f.
Name of Common Variables
CC: C compiler.
CXX: C++ compiler.
CFLAGS: Compiler flags for C.
CXXFLAGS: Compiler flags for C++.
LDFLAGS: Linker options.
LDLIBS: Libraries to link.
OBJS: List of object files.
SRC: List of source files.
Common Automatic Variables
$@:
Represents the full target name.
This is used when you need to refer to the target being generated, like in
commands that create that target.
$<:
Refers to the first prerequisite of the target or the first
dependency in the dependency set.
It is often used in rules where only the first dependency is needed, like
in compiling a source file.
$^:
Contains all the prerequisites for the target, but with any
duplicates removed.
This is useful for linking or when all dependencies are needed.
$+:
Similar to $^, but includes duplicates.
It is often used in cases where the order of prerequisites is significant.
$?:
Contains all prerequisites that are newer than the target.
This is helpful for incremental builds, as it allows make to rebuild only
when necessary.
$*:
Represents the stem of the target name, which is the part matched by
the % in pattern rules.
It is useful when constructing filenames based on patterns.
$%:
This variable is used when the target is an archive.
It represents the member name being processed, which is useful for
commands dealing with archive files.
$$:
Used to represent a literal dollar sign ($).
In commands where a dollar sign is needed (e.g., for shell commands), this
is necessary to prevent it from being interpreted as the beginning of a
variable.
$(a function or a VAR_NAME):
Used for function calls or to evaluate the value of a variable.
Some Common Special Symbols
%: Wildcard used in pattern rules (matches any string).
:: Separates targets from their prerequisites in rules.
$: Introduces a variable or automatic variable (e.g., $@, $<).
@: Represents a command prefix to suppress command echoing.
-: Represents a command prefix that ignores errors for the command without
causing make to exit, thereby allowing subsequent commands to execute.
Environment Variables
1 The Syntax of Referencing an Environment Variable
$(ENV_VAR)
${ENV_VAR}
2 The Syntax of Overriding an Environment Variable
Use an assignment operation to assign a value to it in a Makefile:
ENV_VAR = value, ENV_VAR := value or ENV_VAR ?= value.
Override environment variables when invoking make from the command line:
make ENV_VAR=value, make ENV_VAR:=value or make ENV_VAR?=value.
3 Important Notes
Command-line precedence: Command-line variable assignments (e.g.,
make CC=clang) take precedence over both environment variables and
variables defined in the Makefile.
Override directive: If you want to ensure that a Makefile variable cannot be
overridden by the environment or the command line, you can use the override
directive: override ENV_VAR = value, override ENV_VAR := value or
override ENV_VAR ?= value.
4 Some Common Environment Variables in Makefiles
CC: C compiler (e.g., gcc, clang).
CXX: C++ compiler.
CFLAGS: C compiler flags.
CXXFLAGS: C++ compiler flags.
LDFLAGS: Linker flags.
AR: Archiver (used for static libraries).
LD: Linker.
Functions in Makefiles
Functions for Transforming Text (Usage)
Functions allow you to do text processing in the makefile to compute the
files to operate on or the commands to use in recipes.
You use a function in a function call, where you give the name of the
function and some text (the arguments) for the function to operate on.
The result of the function’s processing is substituted into the makefile at
the point of the call, just as a variable might be substituted.
Function Call Syntax
$(func_name arg_list)
${func_name arg1, arg2, ...}
More Information
If you want to know more about functions in Makefiles, you can refer to
<<GNU Make.pdf>>.
Pattern Rules
Explanation and Usage
In Makefiles, pattern rules allow you to define generalized rules that
apply to multiple targets with similar file extensions or naming
patterns.
Pattern rules use the % symbol to represent a pattern that can be matched
by different file names.
This is particularly useful when you have repetitive build steps, like
compiling several .cpp files into .o files.
Basic Syntax
Code
target-pattern: prerequisite-pattern
command set
Explanation
target-pattern: The pattern for the file(s) to be created. % matches any
part of the target name.
prerequisite-pattern: A pattern for the required dependencies. % in this
part of the rule matches the same text as in the target pattern.
command set: It a set of commands to run when the pattern rule is
triggered.
Notes
The % symbol is the only wildcard symbol available for pattern rules
in Makefiles.
It is specifically designed to represent a pattern wildcard that can match
any part of a file name.
% is used to represent a placeholder for a part of the target or
prerequisite name.
It matches any string of characters in the file names and is used to create
general rules for targets that follow a similar pattern.
The part of the file name that % matches is called the stem.
The stem is used to correlate the target with the prerequisite.
If pattern rules are applied in a chain of dependencies, some implicitly
generated files are deleted at the end of the make process.
How to Understand the Actual Process of Pattern Rules
Create a simple C/C++ project.
Writing multiple simple C/C++ files.
Copy the following code example into your Makefile.
Execute the make command in a terminal.
Check the printed information in the terminal.
The printed information shows the actual process of the pattern rules of the
code example.
Example Without Variables
Example 1: Compiling .cpp files to .o files
Code
%.o: %.cpp
g++ -c $< -o $@
Explanation
This rule applies to all .cpp files in the project and compiles them into
.o object files.
%.o: Target pattern that matches any .o file.
%.cpp: Prerequisite pattern that matches the corresponding .cpp file.
$<: Refers to the first prerequisite (the .cpp file in this case).
$@: Refers to the target (the .o file).
Example 2: Creating Executables
Code
%: %.cpp
g++ $< -o $@
Explanation
This rule compiles .cpp files into executable files directly.
%: Represents any executable name without any suffix.
%.cpp: The prerequisite is a .cpp file with the same base name as the
target.
$<: Refers to the .cpp file.
$@: Refers to the executable.
Example 3: Custom Pattern Rule
Code
build/%: src/%.cpp
g++ -Wall $< -o $@
Explanation
This rule apply a custom command to get dependencies from src/ and generate
targets into build/.
Targets files in the build/ directory.
Matches .cpp files in the src/ directory.
The g++ -Wall command is applied to the matched files.
Pattern rules can be chained, meaning one pattern rule can produce an
intermediate file, which can then be used as a prerequisite for another
pattern rule.
In this case, .cpp files are first compiled into .o files, which are then
linked together to produce the app executable.
Example 5: Using $* for Matching Stems
Code
%.test: %.o
./run_tests $*
Explanation
This pattern rule applies to any .test file, using the corresponding .o
file as a prerequisite. $* represents the stem.
The part of the file name that % matches is called the stem. The stem is
used to correlate the target with the prerequisite.
Example With Variables
Example 1: Using Variables for File Extensions
Code
EXT = cpp
%.o: %.$(EXT)
g++ -c $< -o $@
Explanation
Define a variable to hold file extensions, and then use that variable in a
pattern rule.
EXT = cpp: Defines a variable EXT with the value cpp.
%.o: %.$(EXT): Matches any .cpp file (or whatever extension is stored in
EXT) to produce a .o file.
$< and $@: Standard automatic variables for prerequisites and targets.
Generate shared libraries and link them into a final executable.
$(SHARED_LIBS): $(PREFIX)%.so: %.o: A pattern rule that applies to each
$(PREFIX)%.so file in $(SHARED_LIBS), and each .o file is compiled into
its corresponding $(PREFIX)%.so file.
Example 12: Limitations and an Error Demonstration
The target-pattern itself cannot directly contain a variable that includes a
%.
TARGET_PATTERN: A variable contains a %. This is a wrong demonstration.
How to Process Header Files Effectively
Problem
I have introduced the common knowledge regarding custom rules, implicit
rules, and pattern rules.
However, these rules cannot process header files effectively:
Listing header files as dependencies for each target individually is a
heavy and tedious task.
Utilizing pattern rules and listing header files as common dependencies for
all targets is inefficient because, once header files are modified, all
pattern rules are executed.
The make command cannot determine which header files the C/C++ files depend
on if the header files are not listed as dependencies.
These dependencies are only identified when the compiler processes the C/C++
files, and there isn't a straightforward name-mapping rule.
Consequently, issues arise: if any header files are updated, the make
command remains unaware of the modifications and does nothing to update the
project.
Solution
Code
.PHONY: clean
# Define the compiler variableCC = g++
# List all .cpp filesSRCS = $(wildcard*.cpp)# Generate a list of .o files from SRCSOBJS = $(SRCS:.cpp=.o)# Generate a list of .d files from SRCSDEPS = $(SRCS:.cpp=.d)TARGET = Main.exe
# Default target:$(TARGET): $(OBJS)
$(CC) -o $@ $^
# The rule for # xyz.d is generated by xyz.cpp:%.d: %.cpp
$(CC) -MM $< > [email protected]sed"s,\($*\)\.o[ :]*,\1.o $@ : ,g" < $@.tmp > $@;
rm -f [email protected]# Pattern rules:%.o: %.cpp
$(CC) -c -o $@ $<
clean:
rm -rf *.o *.d $(TARGET)
# Include all .d files:include$(DEPS)
Explanation
gcc -MM and g++ -MM can identify the header dependencies of the source
file.
include $(DEPS): This line includes all the .d files in the Makefile. It
tells make to use the dependency information to track changes to header
files.
How to Precompile Header Files
Problem
Compiling header files repeatedly consumes a lot of time.
Some header files are rarely modified.
They only need to be compiled a few times and can then be reused everywhere.
Solution
.PHONY: clean
# Define the compiler variableCC = g++
# List all .cpp filesSRCS = $(wildcard*.cpp)# Generate a list of .o files from SRCSOBJS = $(SRCS:.cpp=.o)# Generate a list of .h files from SRCSPCHS = $(SRCS:.cpp=.h)PCHS_OUT = $(PCHS).gch)
TARGET = Main.exe
# Default target: depends on $(PCHS_OUT)$(TARGET): $(OBJS)$(PCHS_OUT)
$(CC) -o $@ $(OBJS)
# Rule to precompile the header$(PCHS_OUTPUT): $(PCHS)
$(CXX) $(CXXFLAGS) -o $@ -c $<
# The rule for # xyz.d is generated by xyz.cpp:%.d: %.cpp
$(CC) -MM $< > $@
# Pattern rules:%.o: %.cpp
$(CC) -c -o $@ $<
clean:
rm -rf *.o *.gch $(TARGET)
How to Generate Multiple Targets with One Command Set for Efficient Parallel Compilation
Problem
Nowadays, parallel compilation is popular due to its efficiency.
Most projects and tools support parallel compilation.
However, when using the make tool, utilizing parallel compilation can lead
to errors if a single command set generates multiple targets that other
targets depend on.
One issue is that the previous command set may be executed multiple times.
Another is that subsequent targets may fail to locate their dependencies.
These issues are particularly common when working with lex and yacc (or
flex and bison).
# After, Soultion1.PHONY: clean
Final.txt: Dep1.txt Dep2.txt
cat Dep1.txt Dep2.txt > Final.txt
# Create a transfer tmp.Dep1.txtDep2.txt: Dep.tra.tmp
# @echo "This command set is still executed multiple times, so leaving it empty is the best option."Dep.tra.tmp:
touch Dep1.txt Dep2.txt
touch $@
clean:
@echo "Cleaning ..."
rm -f *.txt *.tra.tmp
# After, Soultion2.PHONY: clean
Final.txt: Dep1.txt Dep2.txt
cat Dep1.txt Dep2.txt > Final.txt
# Create a chain of dependencies.Dep1.txt: Dep2.txt
# @echo "Although this command set is not executed multiple times, leaving it empty is also the best option."Dep2.txt:
touch Dep1.txt Dep2.txt
clean:
@echo "Cleaning ..."
rm -f *.txt
Explanation
Solution1
make does not consider the content of a command set.
It only checks whether the target or dependency files are created or need to
be updated.
To address this, we can use a temporary file as a pseudo-dependency with an
empty command set, acting as a transfer station.
The actual command set used to generate the actual dependency files is
combined with a command to create the temporary file, forming a new command
set for the target, the temporary file.
Solution2
make does not consider the content of a command set.
It only checks whether the target or dependency files are created or need to
be updated.
To address this, we create a chain of dependencies.
Only the last element in the chain has a non-empty command set.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
make -jn
This flag -j allows make to run multiple jobs in parallel, speeding up the
build process by utilizing multiple CPU cores.
The number n (n ≥ 1)following -j (if provided) specifies the maximum
number of jobs to run simultaneously. Without a number n, make will use a
default value.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
make target_name
This command tells make to build a specific target named target_name as
defined in the Makefile.
If target_name has prerequisites, make will first build those before
building the specified target.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
make -C a_path
The -C option changes the directory to a_path before executing the make
command. This allows you to run make in a different directory than the
current one, where the Makefile is located.
If no target is specified, make will build the default target defined in
the Makefile in that directory.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
make -C a_path target_name
Similar to the previous command, this changes the directory to a_path and
then builds the specified target_name defined in the Makefile located in
that directory.
This allows you to target specific builds in different directories.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
make target_name VAR_NAME1="..." VAR_NAME2="..." ...
This command builds the specified target_name while overriding or setting
variables VAR_NAME1 and VAR_NAME2 to the specified values.
These variables can be used in the Makefile to customize the build process or
pass configuration options.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
make -C a_path target_name VAR_NAME1="..." VAR_NAME2="..." ...
This command combines the previous concepts.
It changes the directory to a_path, builds the specified target_name, and
overrides or sets the variables VAR_NAME1 and VAR_NAME2 for that build.
This allows for targeted builds in different directories with specific
configurations.
Notes: If you want to use this command in Makefiles, please use $(MAKE)
instead of make. This is the more commonly used option.
How to Manage Libraries or Dependencies Your Project Denpends On
Add source code for dependencies: Include the source code of your
dependencies in your project and build them from scratch.
Compile them into static or dynamic libraries: Compile the dependencies as
either static libraries or dynamic libraries, depending on your project's
needs.
Link against binaries (if no source code available): If you lack access to
the source code, time to set them up, or prefer not to, linking against
pre-built binaries is also an option.
Build from source if binaries aren't available: If binaries for your
dependencies aren't available, you'll need to download and build their source
code from scratch.
Include directories and libraries: Make sure to include the dependencies'
header directories and library/link directories. Header directories contain
the files required for inclusion in your code, while library directories
contain the pre-built binary files for linking. Header files provide
declarations, and library files contain definitions that link your code with
these binaries.
Static (.lib or .a) vs dynamic libraries (.dll or .so): Linking with
static libraries is faster than dynamic libraries because the C++ linker
performs optimizations during static linking.
Import libraries (xxxdll.lib files): These files contain references to
functions and symbols defined in the xxx.dll files, allowing linking at
compile time, ensuring that the correct function signatures and addresses
are used and allowing the linker to know where to find them at runtime.
Use relative paths: When building dependencies from scratch, use relative
paths instead of absolute paths to ensure portability.
Header file inclusion: Use angle brackets (<>) to include header files if
you’ve specified the paths for the compiler (compiler include paths).
Otherwise, use quotes ("") for relative paths. The search order for quoted
includes starts with relative paths, followed by the main .cpp files, and
finally, the compiler include paths. It’s recommended to use angle brackets
for external headers and quotes for project-specific headers. Prefer compiler
include paths over relative paths to avoid common errors.
Visual Studio project and solution: In Visual Studio, a project contains the
code, resources, and configuration needed to build an executable, library,
or component. A solution acts as a container that groups multiple related
projects, providing organizational structure for managing them.