Timeline
Timeline
2025-11-01
init
This article introduces the basic concepts and application scenarios of Makefile, and discusses in detail its core syntax rules, wildcards, automatic variables, pattern rules, and command execution mechanisms. Additionally, it summarizes advanced usages of Makefile in recursive calls, environment variable export, and parameter passing.
Reference documentation:
There is also a well-translated one:
Introduction to Makefile
Makefiles are used to help decide which parts of a large program need to be recompiled.In the vast majority of cases, only C or C++ files need to be compiled. Other languages typically have their own set of tools similar in purpose to Make.The use of Make is not limited to programming。
Besides Make, there are other popular build systems to choose from, such as SCons, CMake, Bazel, and Ninja. Some code editors, like Microsoft Visual Studio, have their own built-in build tools. For the Java language, build tools like Ant, Maven, and Gradle are available, while other languages like Go and Rust each have their own build tools.
Interpreted languages like Python, Ruby, and JavaScript do not need something like Makefiles. The goal of Makefiles is to compile everything that needs to be compiled based on which files have changed. However,when files in an interpreted language change, recompilation is not needed; the program will use the latest version of the source files at runtime。
Makefile Syntax
A Makefile file consists of a series ofrulesA rule looks like this:
1 | targets: prerequisites |
targetsRefers to file names, multiple file names separated by spaces. Usually, a rule corresponds to only one file.commandsUsually a series of steps to make one or more targets. Theymust start with a tab character, not spaces.prerequisitesAlso file names, multiple file names separated by spaces. Before running the targets’commandssteps, ensure these files exist. They are also calleddependencies。
Example:
1 | blah: blah.o |
-cThe option compiles only without linking,-o fileputs the output of the preceding command into the filefile.
Targets
dependencies
Example:
1 | some_file: other_file |
targetsome_filedependenciesother_file. When we runmake, the default target (i.e.,some_file, because it is the first one) will be built.The build system first checks the dependencies list of the target. If any dependency files are outdated, the build system will first build those dependencies, and only then proceed to the default target.. When runningmakea second time, the commands for the default target and its dependencies will not run again, because both already exist.
Phony targets or virtual targets
1 | some_file: other_file |
Similar to the aboveother_file, such targets are commonly calledphony targetsorvirtual targets。
The All Targets
1 | all: one two three |
Targets executed by default when running the make command
Multiple Targets
1 | all: f1.o f2.o |
When a rule has multiple targets, the commands under this rule will run once for each target.
$@is an automatic variable that refers to the target name.
Automatic Variables
Wildcard*
In Make,%and*are both called wildcards, but they are two completely different things.*will search your file system to match filenames.
It is recommended to always** usewildcardfunction to wrap it**, otherwise you might fall into a trap.
Without usingwildcardwrapped*has no merit other than causing confusion.
1 | # Print file information for each .c file |
*cannot be used directly in variable definitions.When
*When no file matches, it remains unchanged (unless it iswildcardwrapped by a function).
1 | thing_wrong := *.o # Please do not do this! '*.o' will not be replaced with the actual filename |
Wildcard%
- When used in “match” mode, itmatches one or more characters in a string, this matching is called stem matching.
- When used in “replace” mode, it replaces the matched stem.
%Mostly used in rule definitions and some specific functions.
Automatic variables
automatic variables
Although there are many automatic variables, only a few are commonly used
$@
of the current ruletarget file name (target)
$?
in the current rulelist of dependency files newer than the target file
Example: For the rule:foo: a.c b.c, ifb.cis updated →$?→b.c
$<
of the current rulefirst dependency file (prerequisite)
$^
of the current ruleall dependency files (deduplicated)
Fancy Rules
implicit rules
Make loves C compilation, and every time it expresses its affection, it exhibits some “confusing behaviors.” The most perplexing part might be its magical rules, which Make calls “implicit rules.” They are not recommended for use.
The implicit rules are listed below:
- When compiling C programs: use
$(CC) -c $(CPPFLAGS) $(CFLAGS)command of the form,n.owill be automatically generated byn.c. - When compiling a C++ program: use the
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)command of the form,n.owill be automatically generated byn.ccorn.pp. - When linking a single object file: by running the
$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)command,nwill be automatically generated byn.o.
The meanings of the variables used in the above implicit rules are as follows:
CC: the program for compiling C programs, default isccCXX: the program for compiling C++ programs, default isg++CFLAGS: Additional flags for the C compilerCXXFLAGS: Additional flags for the C++ compilerCPPFLAGS: Additional flags for the C preprocessorLDFLAGS: Additional flags for the compiler when it should invoke the linker
Example of implicit rules:
1 | CC = gcc # Flag for implicit rules |
Static pattern rules
1 | targets ...: target-pattern: prereq-patterns ... |
Its essence is: The given targettargetis matched bytarget-patternintargets(using wildcard%). The matched content is called the stem. Then,the stem is substituted intoprereq-patternto generate theprerequisitespart of the target.
A typical use case of static pattern rules is to.cfile is compiled into.ofile.
Manualmethod:
1 | objects = foo.o bar.o all.o |
Static patternmethod:
1 | objects = foo.o bar.o all.o |
Explanation:
1 | $(objects): %.o: %.c |
This line is crucial; it means:
For
$(objects)(i.e., foo.o, bar.o, all.o), if there is a matching rule%.o, then it depends on the corresponding%.cfile.
For example:
foo.odepends onfoo.cbar.odepends onbar.call.odependencyall.c
This is actually equivalent to:
1 | foo.o: foo.c |
But generating it automatically in one line demonstrates thepower of wildcard rules。
1 | all.c: |
Whenmakefindsfoo.oneedsfoo.cand it does not exist,
it triggers the%.c:rule to create an empty filefoo.c
Whenmakeneedsall.o, it triggers theall.c:rule (generating a file with content)
It can also be written like this:
1 | # Define target file list |
Static pattern rules and filters
Example:
1 | obj_files = foo.result bar.o lose.o |
Declare phony targets
1 |
- Indicates
allandcleanare not actual files, but logical commands. - Avoid confusion caused by a file named
allin make.
Main target (entry point)
1 | all: $(obj_files) |
default target
alldepends on allobj_files。When executing
make, Make will build:- foo.result
- bar.o
- lose.o
Usingfilterrules to filter different types of files
For.ofile rules:
1 | $(filter %.o,$(obj_files)): %.o: %.c |
Explanation:
$(filter %.o,$(obj_files))
→ Fromobj_filesfilter out.ofiles ending with:1
bar.o lose.o
After expansion, it is equivalent to:
1
2bar.o lose.o: %.o: %.c
@echo "target: $@ prereq: $<"%.o: %.cisstatic pattern rule:- The target is like
xxx.o, depends on the same-namedxxx.c。 $@: indicates the target filename (e.g.,bar.o)$<: indicates the first dependency file (e.g.,bar.c)
- The target is like
This rule does not actually compile, it just prints information:
1 | target: bar.o prereq: bar.c |
For.resultfile’s rule:
1 | $(filter %.result,$(obj_files)): %.result: %.raw |
Similarly:
$(filter %.result,$(obj_files))→ the result isfoo.resultAfter expansion, it is equivalent to:
1
2foo.result: %.result: %.raw
@echo "target: $@ prereq: $<"
Meaning: to generatefoo.result, needfoo.raw
Pattern rule
An example:
1 | # Define a pattern rule that compiles every .c file into a .o file |
A pattern rule contains a%, this%matches any non-empty string, other characters match themselves. In a pattern rule,prerequisitein%represents the%same stem matched by .
Another example:
1 | # Define a pattern rule that has no pattern in the prerequisites. |
Double-colon rules
1 | target :: prerequisites |
Meaning:
- A target can be defined multiple times, with each rule existing independently.
- Whenever a dependency file of a rule is updated, the commands of that rule are executed separately.
Example:
1 | foo :: a |
Execute:
1 | $ make foo |
Assume:
ais newer thanfoo(updated)bthanfooold (not updated)
Output result:
1 | rule 1 triggered by a |
Ifa、bare all newer thanfoonew, then output:
1 | rule 1 triggered by a |
Commands and Execution
Echo/Silent Commands
Adding a@symbol before a command will suppress its output.
You can also usemake -sbefore each command to add@。
1 | all: |
Command Execution
Each command runs in a new shell (or its effect is equivalent to running in a new shell).
1 | all: |
Change Default Shell
The system default shell is/bin/sh, which can be changed by modifying the value of theSHELLvariable:
1 | SHELL=/bin/bash |
Error handling:-k,-iand-
make -kwill cause the build to continue even if errors occur. Often used to view all errors from Make at once.
Adding-before a command suppresses errors.
make -iis equivalent to adding-。
1 | one: |
Interrupting or killingmake
Duringmake, ifctrl+cis used, the newly made target will be deleted.
makeRecursive usage of
To recursively apply a makefile, use$(MAKE)instead ofmake, because it passes build flags, while using$(MAKE)the variable in this command line will not apply these flags.
1 | new_contents = "hello:\n\ttouch inside_file" |
| Syntax | Behavior | Recommended |
|---|---|---|
make | Normal command, does not inherit make’s options | ❌ |
$(MAKE) | Special variable, representing the “current make program” itself, automatically inherits options | ✅ |
Environment variable
export
Directiveexportcarries a variable and is visible to childmakecommands.
In the following example, the variablecoolyis exported so that the makefile in the subdirectory can use it.
1 | new_contents = "hello:\n\\techo \$$(cooly)" |
Passing variables to the shell
Variables must also be exported to pass them to the shell.
1 | one=this will only work locally |
.EXPORT_ALL_VARIABLES
.EXPORT_ALL_VARIABLES can export all variables.
1 | .EXPORT_ALL_VARIABLES: |
Pass tomakeparameters
| Options / Usage | Full command | Description | Example |
|---|---|---|---|
-n/--dry-run | make --dry-run | Only display the commands that will be executed, but do not actually run them. Used for debugging Makefiles. | make --dry-run all(view execution flow) |
-t/--touch | make --touch | Mark target files as “up-to-date” (update timestamps) without actually executing commands. Often used to skip actual builds. | make --touch all |
-o <file>/--old-file=<file> | make --old-file=foo.o | Specify a file to be treated as “old”, so that targets depending on it will not be rebuilt. | make --old-file=main.o |
Multiple targets can be passed to
make, for examplemake clean run testwill run sequentiallyclean、run、test。
variable
type of variable
- recursive variable(using
=)- variables are looked up only when the command is executed, not when defined - simply expanded variable(using
:=) - like ordinary imperative programming—only currently defined variables are expanded
example:
1 | # Recursive variable. will print later |
1 | one = hello |
?=
assign a value to a variable only if it hasn’t been set yet; otherwise ignore.
1 | one = hello |
+=
used to append to a variable’s value:
1 | foo := start |
command-line variable
You can useoverrideto override variables from the command line.
If we execute a command like this using the following makefilemake option_one=hi, then the variableoption_onewill be overridden.
1 | # Overrides command line arguments |
#define defines a command list
It has nothing to do with the functiondefine.
Example:
1 | one = export blah="I was set!"; echo $$blah |
Note here that it is slightly different from the scenario where multiple commands are separated by semicolons, because in the former case, each command runs in a separate shell as expected.
That is:Each command line(in the recipe) is executed in a newindependent shell instance. Therefore, modifications such as environment variables or the current directory do not automatically carry over to the next line.
For example:
1 | all: |
Nothing will be printed because:
- Line 1 runs in one shell and sets an environment variable.
- Line 2 runs in another shell and cannot see the previous shell’s environment.
Target-specific variables
We can assign variables for specific targets.
1 | all: one = cool |
Pattern-specific variables
We can assign variables for specific targetpatterns.
1 | %.c: one = cool |
Conditional statements in Makefile
if/else
1 | foo = ok |
strip checks if a variable is empty
1 | nullstring = |
ifdef checks if a variable is defined
ifdefdoes not expand variable references; it only checks if the variable’s content is defined.
1 | bar = |
$(makeflags)
MAKEFLAGSisGNU Make’s built-in variables, it automatically saves the currentmakeall command-line options.
| invocation method | MAKEFLAGScontent |
|---|---|
make | (empty) |
make -i | i |
make -k | k |
make -ik | ik |
make -n -s | ns |
make -j4 | j4(with numeric arguments) |
1 | bar = |
$(findstring i, $(MAKEFLAGS))
- This is one of GNU Make’s built-in functions.
- Function: find the first string in the second string.
- If found, return the first string (here
"i"); - If not found, return an empty string.
ifneq (,$(...))
- Syntax:
ifneq (arg1, arg2)
means “if arg1 and arg2 are not equal” then execute the following statements. - Here it is written as
(, $(findstring ...))
meaning "if the result of findstringnon-empty”。
Therefore the whole sentence is logically equivalent to:
If
MAKEFLAGScontains the letteri, then execute echo.
Functions
Functionsare mainly used for text processing. The syntax for a function call is$(fn, arguments)or${fn, arguments}. You can use built-in functionscallto create your own functions. Make has a large number of built-in functions.
1 | bar := ${subst not, totally, "I am not superman"} |
If you want to replace spaces or commas, you need to use variables:
1 | comma := , |
Do notinclude spaces in arguments after the first argument, as they will be treated as part of the string.
1 | comma := , |
patsubst string substitution
$(patsubst pattern,replacement,text)did the following things:
"Find matching space-separated words in the text, replace them with
replacement. Here,patterncan contain a%as a wildcard to match any number of any characters in a word. Ifreplacementalso contains a%, then the content it represents will be replaced bypatternin%matched content. Onlypatternandreplacementthe first%will exhibit this behavior; any subsequent%will remain unchanged.
$(text:pattern=replacement)is a shorthand notation.
There is also a shorthand form that only replaces suffixes:$(text:suffix=replacement), no wildcards are used here%。
Note: In the shorthand form,do not add extra spaces, it will be treated as a search or replacement item.
1 | foo := a.o b.o l.a c.o |
foreach
The functionforeachlooks like this:$(foreach var,list,text), it is used toconvert a list of words (space-separated) into another。
varrepresents each word in the looplistrepresents the variable to loop overtextis used to expand each word.
Example: Append an exclamation mark after each word:
1 | foo := who are you |
if
1 | $(if condition, then-part, else-part) |
ifThe function is used tocheck if its first argument is non-empty。if non-empty, run the second argument; otherwise, run the third。
1 | foo := $(if this-is-not-empty,then!,else!) |
call
Make supports creating basic functions. You simply “define” a function by creating a variable, but with parameters$(0)、$(1)etc. Then, you can use a special functioncallto call it, with the syntax$(call variable,param,param)。$(0)is the variable name, and$(1)、$(2)etc. are the parameters.
1 | sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3) |
shell
shell- calls the shell, butit replaces newlines with spaces in the output.
1 | all: |
Other Features
include
includeThe directive tellsmaketo read other makefiles; it is a line in the makefile as follows:
1 | include filenames... |
vpath
vpathThe directive** is used to specify certainprerequisiteslocations**, using the formatvpath <pattern> <directories, space/colon separated>。
vpathtells Make:which directories to search when dependency files are not in the current directory。
<pattern>can be used in%, used to match 0 or more characters.
You can also use the variableVPATHto perform this operation globally.
1 | vpath %.h ../headers ../other-directory |
1 | vpath %.h ../headers ../other-directory |
Meaning: For all files ending with .h (i.e., header files), if not found in the current directory, search sequentially in../headersand../other-directoryto search.
Multi-line Processing
When a command is too long, a backslash (\) allows us to use a multi-line writing format.
1 | some_file: |
.PHONY
Adding.PHONYto a target willprevent a phony target from being recognized as a file name。
In the following example, even if the filecleanis created,make cleanwill still run..PHONYVery easy to use.
1 | some_file: |
.DELETE_ON_ERROR
If a command returns a non-zero exit code, thenmakewill stop running the corresponding rule (and propagate to its dependencies). If a rule fails to build due to the above situation, then applying.DELETE_ON_ERRORwill cause the target file of this rule to be deleted.
Unlike.PHONY,.DELETE_ON_ERRORis effective for all targets. Always use.DELETE_ON_ERRORis a good choice, even though for historical reasons,makedoes not support it.
1 | .DELETE_ON_ERROR: |
Example
1 | TARGET_EXEC := final_program |
