Cover image for Makefile

Makefile


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
2
3
4
targets: prerequisites
command
command
command
  • 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
2
3
4
5
6
7
8
blah: blah.o
cc blah.o -o blah # Runs third

blah.o: blah.c
cc -c blah.c -o blah.o # Runs second

blah.c:
echo "int main() { return 0; }" > blah.c # Runs first

-cThe option compiles only without linking,-o fileputs the output of the preceding command into the filefile.

Targets

dependencies

Example:

1
2
3
4
5
6
7
some_file: other_file
echo "This will run second, because it depends on other_file"
touch some_file

other_file:
echo "This will run first"
touch 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
2
3
4
5
some_file: other_file
touch some_file

other_file:
echo "nothing"

Similar to the aboveother_file, such targets are commonly calledphony targetsorvirtual targets

The All Targets

1
2
3
4
5
6
7
8
9
10
11
all: one two three

one:
touch one
two:
touch two
three:
touch three

clean:
rm -f one two three

Targets executed by default when running the make command

Multiple Targets

1
2
3
4
5
6
7
8
9
all: f1.o f2.o

f1.o f2.o:
echo $@
# Equivalent to:
# f1.o
# echo $@
# f2.o
# echo $@

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
2
3
# Print file information for each .c file
print: $(wildcard *.c)
ls -la $?

*cannot be used directly in variable definitions.

When*When no file matches, it remains unchanged (unless it iswildcardwrapped by a function).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
thing_wrong := *.o # Please do not do this! '*.o' will not be replaced with the actual filename
thing_right := $(wildcard *.o)

all: one two three four

# Fails because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)

# If no file matches this pattern, it will remain as *.o :(
two: *.o

# Works as expected! In this case, nothing will be executed
three: $(thing_right)

# Same as rule three
four: $(wildcard *.o)

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 iscc
  • CXX: the program for compiling C++ programs, default isg++
  • CFLAGS: Additional flags for the C compiler
  • CXXFLAGS: Additional flags for the C++ compiler
  • CPPFLAGS: Additional flags for the C preprocessor
  • LDFLAGS: Additional flags for the compiler when it should invoke the linker

Example of implicit rules:

1
2
3
4
5
6
7
8
9
10
11
12
CC = gcc # Flag for implicit rules
CFLAGS = -g # Flag for implicit rules. Turn on debug info

# Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
blah: blah.o

blah.c:
echo "int main() { return 0; }" > blah.c

clean:
rm -f blah*

Static pattern rules

1
2
targets ...: target-pattern: prereq-patterns ...
commands

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
objects = foo.o bar.o all.o
all: $(objects)

# These files compile via implicit rules
foo.o: foo.c
bar.o: bar.c
all.o: all.c

all.c:
echo "int main() { return 0; }" > all.c

%.c:
touch $@

clean:
rm -f *.c *.o all

Static patternmethod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
objects = foo.o bar.o all.o
all: $(objects)

# These files compile via implicit rules
# Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
$(objects): %.o: %.c

all.c:
echo "int main() { return 0; }" > all.c

%.c:
touch $@

clean:
rm -f *.c *.o all

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.c
  • bar.odepends onbar.c
  • all.odependencyall.c

This is actually equivalent to:

1
2
3
foo.o: foo.c
bar.o: bar.c
all.o: all.c

But generating it automatically in one line demonstrates thepower of wildcard rules

1
2
3
4
5
all.c:
echo "int main() { return 0; }" > all.c

%.c:
touch $@

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Define target file list
objects = foo.o bar.o all.o

# Final target
all: $(objects)
$(CC) -o all $(objects)

# Generic compilation rule: any .o is compiled from the corresponding .c
%.o: %.c
$(CC) -c $< -o $@

# Generate all.c (if not exists)
all.c:
echo "int main() { return 0; }" > all.c

# If no corresponding .c file exists, automatically create an empty file
%.c:
touch $@

# Clean
clean:
rm -f *.c *.o all

Static pattern rules and filters

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c

.PHONY: all
all: $(obj_files)

$(filter %.o,$(obj_files)): %.o: %.c
echo "target: $@ prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw
echo "target: $@ prereq: $<"

%.c %.raw:
touch $@

clean:
rm -f $(src_files)

Declare phony targets

1
.PHONY: all clean
  • Indicatesallandcleanare not actual files, but logical commands.
  • Avoid confusion caused by a file namedallin make.

Main target (entry point)

1
all: $(obj_files)
  • default targetalldepends on allobj_files

  • When executingmake, Make will build:

    • foo.result
    • bar.o
    • lose.o

Usingfilterrules to filter different types of files

For.ofile rules:

1
2
$(filter %.o,$(obj_files)): %.o: %.c
@echo "target: $@ prereq: $<"

Explanation:

  • $(filter %.o,$(obj_files))
    → Fromobj_filesfilter out.ofiles ending with:

    1
    bar.o lose.o
  • After expansion, it is equivalent to:

    1
    2
    bar.o lose.o: %.o: %.c
    @echo "target: $@ prereq: $<"
  • %.o: %.cisstatic pattern rule

    • The target is likexxx.o, depends on the same-namedxxx.c
    • $@: indicates the target filename (e.g.,bar.o
    • $<: indicates the first dependency file (e.g.,bar.c

This rule does not actually compile, it just prints information:

1
2
target: bar.o prereq: bar.c
target: lose.o prereq: lose.c

For.resultfile’s rule:

1
2
$(filter %.result,$(obj_files)): %.result: %.raw
@echo "target: $@ prereq: $<"

Similarly:

  • $(filter %.result,$(obj_files))→ the result isfoo.result

  • After expansion, it is equivalent to:

    1
    2
    foo.result: %.result: %.raw
    @echo "target: $@ prereq: $<"

Meaning: to generatefoo.result, needfoo.raw

Pattern rule

An example:

1
2
3
# Define a pattern rule that compiles every .c file into a .o file
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

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
2
3
4
# Define a pattern rule that has no pattern in the prerequisites.
# This just creates empty .c files when needed.
%.c:
touch $@

Double-colon rules

1
2
target :: prerequisites
commands

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
2
3
4
5
foo :: a
echo "rule 1 triggered by a"

foo :: b
echo "rule 2 triggered by b"

Execute:

1
$ make foo

Assume:

  • ais newer thanfoo(updated)
  • bthanfooold (not updated)

Output result:

1
rule 1 triggered by a

Ifabare all newer thanfoonew, then output:

1
2
rule 1 triggered by a
rule 2 triggered by b

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
2
3
all: 
@echo "This make line will not be printed"
echo "But this will"

Command Execution

Each command runs in a new shell (or its effect is equivalent to running in a new shell).

1
2
3
4
5
6
7
8
9
10
11
all: 
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd`

# This cd command affects the next because they are on the same line
cd ..;echo `pwd`

# Same as above
cd ..; \
echo `pwd`

Change Default Shell

The system default shell is/bin/sh, which can be changed by modifying the value of theSHELLvariable:

1
2
3
4
SHELL=/bin/bash

cool:
echo "Hello from 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
2
3
4
one:
# This error will be printed but ignored, and make will continue to run
-false
touch 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
2
3
4
5
6
7
8
new_contents = "hello:\n\ttouch inside_file"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)

clean:
rm -rf subdir
SyntaxBehaviorRecommended
makeNormal 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new_contents = "hello:\n\\techo \$$(cooly)"

all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)

# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly

clean:
rm -rf subdir

Passing variables to the shell

Variables must also be exported to pass them to the shell.

1
2
3
4
5
6
7
8
one=this will only work locally
export two=we can run subcommands with this

all:
@echo $(one)
@echo $$one
@echo $(two)
@echo $$two

.EXPORT_ALL_VARIABLES

.EXPORT_ALL_VARIABLES can export all variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"

cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly

all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)

clean:
rm -rf subdir

Pass tomakeparameters

Options / UsageFull commandDescriptionExample
-n/--dry-runmake --dry-runOnly 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/--touchmake --touchMark 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.oSpecify 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 tomake, for examplemake clean run testwill run sequentiallycleanruntest

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
2
3
4
5
6
7
8
9
10
# Recursive variable. will print later
one = one ${later_variable}
# Simply expanded variable. will not print later
two := two ${later_variable}

later_variable = later

all:
echo $(one)
echo $(two)
1
2
3
4
5
6
one = hello
# one gets defined as a simply expanded variable (:=) and thus can handle appending
one := ${one} there

all:
echo $(one)
  • ?=

assign a value to a variable only if it hasn’t been set yet; otherwise ignore.

1
2
3
4
5
6
7
one = hello
one ?= will not be set
two ?= will be set

all:
echo $(one)
echo $(two)
  • +=

used to append to a variable’s value:

1
2
3
4
5
foo := start
foo += more

all:
echo $(foo)

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
2
3
4
5
6
7
# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_two = not_override
all:
echo $(option_one)
echo $(option_two)

#define defines a command list

It has nothing to do with the functiondefine.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
one = export blah="I was set!"; echo $$blah

define two
export blah=set
echo $$blah
endef

# One and two are different.

all:
@echo "This prints 'I was set'"
@$(one)
@echo "This does not print 'I was set' because each command runs in a separate shell"
@$(two)

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
2
3
all:
export FOO=bar
echo $$FOO

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
2
3
4
5
6
7
all: one = cool

all:
echo one is defined: $(one)

other:
echo one is nothing: $(one)

Pattern-specific variables

We can assign variables for specific targetpatterns.

1
2
3
4
5
6
7
%.c: one = cool

blah.c:
echo one is defined: $(one)

other:
echo one is nothing: $(one)

Conditional statements in Makefile

if/else

1
2
3
4
5
6
7
8
foo = ok

all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif

strip checks if a variable is empty

1
2
3
4
5
6
7
8
9
10
nullstring =
foo = $(nullstring) # end of line; there is a space here

all:
ifeq ($(strip $(foo)),)
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
echo "nullstring doesn't even have spaces"
endif

ifdef checks if a variable is defined

ifdefdoes not expand variable references; it only checks if the variable’s content is defined.

1
2
3
4
5
6
7
8
9
10
bar =
foo = $(bar)

all:
ifdef foo
echo "foo is defined"
endif
ifdef bar
echo "but bar is not"
endif

$(makeflags)

MAKEFLAGSisGNU Make’s built-in variables, it automatically saves the currentmakeall command-line options.

invocation methodMAKEFLAGScontent
make(empty)
make -ii
make -kk
make -ikik
make -n -sns
make -j4j4(with numeric arguments)
1
2
3
4
5
6
7
8
bar =
foo = $(bar)

all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
echo "i was passed to MAKEFLAGS"
endif

$(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:

IfMAKEFLAGScontains 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
2
3
bar := ${subst not, totally, "I am not superman"}
all:
@echo $(bar)

If you want to replace spaces or commas, you need to use variables:

1
2
3
4
5
6
7
8
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))

all:
@echo $(bar)

Do notinclude spaces in arguments after the first argument, as they will be treated as part of the string.

1
2
3
4
5
6
7
8
9
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))

all:
# Output is ", a , b , c". Notice the spaces introduced
@echo $(bar)

patsubst string substitution

$(patsubst pattern,replacement,text)did the following things:

"Find matching space-separated words in the text, replace them withreplacement. 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
2
3
4
5
6
7
8
9
10
11
foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)

all:
echo $(one)
echo $(two)
echo $(three)

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 loop

  • listrepresents the variable to loop over

  • textis used to expand each word.

Example: Append an exclamation mark after each word:

1
2
3
4
5
6
7
foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)

all:
# Output is "who! are! you!"
@echo $(bar)

if

1
$(if condition, then-part, else-part)

ifThe function is used tocheck if its first argument is non-emptyif non-empty, run the second argument; otherwise, run the third

1
2
3
4
5
6
7
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)

all:
@echo $(foo)
@echo $(bar)

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
2
3
4
5
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)

all:
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
@echo $(call sweet_new_fn, go, tigers)

shell

shell- calls the shell, butit replaces newlines with spaces in the output.

1
2
all: 
@echo $(shell ls -la) # Very ugly because the newlines are gone!

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
2
3
4
5
6
7
8
9
10
11
12
13
14
vpath %.h ../headers ../other-directory

some_binary: ../headers blah.h
touch some_binary

../headers:
mkdir ../headers

blah.h:
touch ../headers/blah.h

clean:
rm -rf ../headers
rm -f some_binary
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
2
3
some_file: 
echo This line is too long, so \
it is broken up into multiple lines

.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
2
3
4
5
6
7
8
some_file:
touch some_file
touch clean

.PHONY: clean
clean:
rm -f some_file
rm -f clean

.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
2
3
4
5
6
7
8
9
10
.DELETE_ON_ERROR:
all: one two

one:
touch one
false

two:
touch two
false

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
TARGET_EXEC := final_program

BUILD_DIR := ./build
SRC_DIRS := ./src

# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')

# String substitution for every C/C++ file.
# As an example, hello.cpp turns into ./build/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)

# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)

# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

# The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
CPPFLAGS := $(INC_FLAGS) -MMD -MP

# The final build step.
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
$(CC) $(OBJS) -o $@ $(LDFLAGS)

# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
mkdir -p $(dir $@)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
mkdir -p $(dir $@)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@


.PHONY: clean
clean:
rm -r $(BUILD_DIR)

# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS)