Post

Makefile


Makefile


basic

  • Makefiles are used to help decide which parts of a large program need to be recompiled.
  • In the vast majority of cases, C or C++ files are compiled.
  • Other languages typically have their own tools that serve a similar purpose as Make.
  • It can be used beyond programs too, when you need a series of instructions to run depending on what files have changed. This tutorial will focus on the C/C++ compilation use case.

Here’s an example dependency graph that you might build with Make. If any file’s dependencies changes, then the file will get recompiled:

dependency_graph

alternatives to Make

Interpreted languages like Python, Ruby, and Javascript don’t require an analogue to Makefiles.

  • The goal of Makefiles is to compile whatever files need to be compiled, based on what files have changed.
  • But when files in interpreted languages change, nothing needs to get recompiled. When the program runs, the most recent version of the file is used.

Makefile Syntax

A Makefile consists of a set of rules

  • The targets
    • are file names, separated by spaces.
    • Typically, there is only one per rule.
    • 一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)
  • The prerequisites / dependencies
    • are also file names, separated by spaces.
    • These files need to exist before the commands for the target are run.
    • 要生成那个target所需要的文件或是目标。
  • The commands
    • a series of steps typically used to make the target(s).
    • These need to start with a tab character ,
    • not spaces.
    • make需要执行的命令。(任意的Shell命令)
1
2
3
4
5
6
7
# A rule generally looks like this:
targets: prerequisites
   command
   command
   command
clean:
   command

这是一个文件的依赖关系

  • target这一个或多个的目标文件依赖于prerequisites中的文件,
  • 其生成规则定义在command中。
  • prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
  • 这就是Makefile的规则。也就是Makefile中最核心的内容。
  • make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,
  • 对于所定义的命令的错误,或是编译不成功,make根本不理。
  • make只管文件的依赖性
    • 如果在找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

clean不是一个文件,它只不过是一个动作名字

  • 其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。
  • 要执行其后的命令,就要在make命令后明显得指出这个lable的名字。
  • 这样的方法可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
  • 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,
    • 要make执行。即命令——“make clean”

Run Examples

put the contents in a file called Makefile, and in that directory run the command make.

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 1.
# Makefile
hello:
    echo "hello world"

$ make
# echo "hello world"
# hello world



# 2.
# It have 3 separate rules,
# - When you run `make blah` in the terminal, it will build a program called `blah` in a series of steps:
#   - Make is given `blah` as the target, so it first searches for this target
#   - `blah` requires `blah.o`, so make searches for the `blah.o` target
#   - `blah.o` requires `blah.c`, so make searches for the `blah.c` target
#   - `blah.c` has no dependencies, so the `echo` command is run
#   - The `cc -c` command is then run, because all of the `blah.o` dependencies are finished
#   - The top `cc` command is run, because all the `blah` dependencies are finished
#   - That's it: `blah` is a compiled c program
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
# echo "int main() {return 0;}" > blah.c
# cc -c blah.c -o blah.o
# cc blah.o -o blah


# 3.
# This makefile has a single target, called `some_file`.
# The default target is the first target, so in this case `some_file` will run.
some_file:
    echo "This line will always print"
# first time: This file will make `some_file`
# the second time notice run the made: `make: 'some_file' is up to date.`

# 4.
# Here, the target `some_file` "depends" on `other_file`.
# When we run `make`, the default target (`some_file`) will get called.
# It will first look at its list of dependencies, and if any of them are older, it will first run the targets for those dependencies, and then run itself.
# The second time this is run, neither target will run because both targets exist.

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 ot


# 5.
# `clean` is often used as a target that removes the output of other targets, but it is not a special word in `make`.

some_file:
    touch some_file

clean:
    rm -f some_file

Variables

Variables can only be strings. Here’s an example of using them:

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
files = file1 file2

some_file: $(files)
    echo "Look at this variable: " $(files)
    touch some_file
file1:
    touch file1
file2:
    touch file2
clean:
    rm -f file1 file2 some_file
# touch file1
# touch file2
# echo "Look at this variable: " file1 file2
# Look at this variable:  file1 file2
# touch some_file



# Reference variables using ${} or $()
x = dude
all:
    echo $(x)
    echo ${x}

    # Bad practice, but works
    echo $x

Targets

all target

Make a all target.

  • Making multiple targets and you want all of them to run
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

Multiple targets

Multiple targets

  • When there are multiple targets for a rule
  • the commands will be run for each target
  • $@ is an automatic variable that contains the target name.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
all: f1.o f2.o

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

# echo f1.o
# f1.o
# echo f2.o
# f2.o

Automatic Variables and Wildcards

Both * and % are called wildcards in Make, but they mean entirely different things.

* Wildcard

  • * search your filesystem for matching filenames.
  • suggest that always wrap it in the wildcard function
  • * may be used in the target, prerequisites, or in the wildcard function.
  • Danger:
    • * may not be directly used in a variable definitions
    • When * matches no files, it is left as it is (unless run in the wildcard function)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Print out file information about every .c file
print: $(wildcard *.c)
    ls -la  $?

# Don't do this! '*' will not get expanded
thing_wrong := *.o
thing_right := $(wildcard *.o)


all: one two three four

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

# Stays as *.o if there are no files that match this pattern :(
two: *.o

# Works as you would expect! In this case, it does nothing.
three: $(thing_right)

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

% Wildcard

  • “matching” mode
    • it matches one or more characters in a string.
    • This match is called the stem.
  • “replacing” mode,
    • it takes the stem that was matched and replaces that in a string.
  • % is most often used in rule definitions and in some specific functions.

See these sections on examples of it being used:

  • Static Pattern Rules
  • Pattern Rules
  • String Substitution
  • The vpath Directive

Automatic Variables

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
hey: one two
    # Outputs "hey", since this is the first target
    echo $@
    # Outputs all prerequisites newer than the target
    echo $?
    # Outputs all prerequisites
    echo $^
    touch hey
one:
    touch one
two:
    touch two
clean:
    rm -f hey one two

# touch one
# touch two
# # Outputs "hey", since this is the first target
# echo hey
# hey
# # Outputs all prerequisites newer than the target
# echo one two
# one two
# # Outputs all prerequisites
# echo one two
# one two
# touch hey


Fancy Rules


Static Pattern Rules

  • a new type of rule
1
2
3
4
5
6
targets ...: target-pattern: prereq-patterns ...
   commands

# the given target is matched by the target-pattern (via a `%` wildcard).
# Whatever was matched is called the stem
# The stem is then substituted into the prereq-pattern, to generate the target's prereqs.

A typical use case is to compile .c files into .o files.

  1. Here’s the manual way
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
# echo "int main() { return 0; }" > all.c
# cc    -c -o all.o all.c
# touch foo.c
# cc    -c -o foo.o foo.c
# touch bar.c
# cc    -c -o bar.o bar.c
# cc   all.o foo.o bar.o   -o all

  1. more efficient way , using a static pattern rule:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

Static Pattern Rules and Filter

While I introduce functions later on, I’ll forshadow what you can do with them. The filter function can be used in Static pattern rules to match the correct files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In this example, I made up the `.raw` and `.result` extensions.

obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c

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)

Implicit Rules

Perhaps the most confusing part of make is the magic rules and variables that are made. Here’s a list of implicit rules:

  • Compiling a C program: n.o is made automatically from n.c with a command of the form $(CC) -c $(CPPFLAGS) $(CFLAGS)
  • Compiling a C++ program: n.o is made automatically from n.cc or n.cpp with a command of the form $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
  • Linking a single object file: n is made automatically from n.o by running the command $(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)

As such, the important variables used by implicit rules are:

  • CC: Program for compiling C programs; default cc
  • CXX: Program for compiling C++ programs; default G++
  • CFLAGS: Extra flags to give to the C compiler
  • CXXFLAGS: Extra flags to give to the C++ compiler
  • CPPFLAGS: Extra flags to give to the C preprosessor
  • LDFLAGS: Extra flags to give to compilers when they are supposed to invoke the linker
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*

Pattern Rules

Pattern rules

  • A way to define your own implicit rules
  • A simpler form of static pattern rules
  • Pattern rules contain a % in the target.
    • % matches any nonempty string, and the other characters match themselves.
    • % in a prerequisite of a pattern rule stands for the same stem that was matched by the % in the target.
1
2
3
4
5
6
7
8
# Define a pattern rule that compiles every .c file into a .o file
%.o : %.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

# Define a pattern rule that has no pattern in the prerequisites.
# This just creates empty .c files when needed.
%.c:
   touch $@

Functions for String Substitution and Analysis

  1. $(subst from,to,text)
    • textual replacement on the text text: each occurrence of from is replaced by to.
    • The result is substituted for the function call
1
2
$(subst ee,EE,feet on the street)
# produces the value `fEEt on the strEEt`.
  1. $(patsubst pattern,replacement,text)
    • Finds whitespace-separated words in text that match pattern and replaces them with replacement.
    • pattern may contain a % which acts as a wildcard, matching any number of any characters within a word.
    • If replacement also contains a %, the % is replaced by the text that matched the % in pattern. Only the first % in the pattern and replacement is treated this way; any subsequent % is unchanged.

    • % characters in patsubst function invocations can be quoted with preceding backslashes (\). Backslashes that would otherwise quote % characters can be quoted with more backslashes.
    • Backslashes that quote % characters or other backslashes are removed from the pattern before it is compared file names or has a stem substituted into it.
    • Backslashes that are not in danger of quoting % characters go unmolested.
    • For example
    • the pattern the\%weird\\%pattern\\ has the%weird\ preceding the operative % character, and pattern\\ following it. The final two backslashes are left alone because they cannot affect any % character.
    • Whitespace between words is folded into single space characters; leading and trailing whitespace is discarded.
1
2
$(patsubst %.c,%.o,x.c.c bar.c)
# produces the value `x.c.o bar.o`.
  1. Substitution references
    • simpler way to get the effect of the patsubst function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(var:pattern=replacement)
# is equivalent to
$(patsubst pattern,replacement,$(var))
The second shorthand simplifies one of the most common uses of patsubst: replacing the suffix at the end of file names.

$(var:suffix=replacement)
# is equivalent to
$(patsubst %suffix,%replacement,$(var))


objects = foo.o bar.o baz.o
# To get the list of corresponding source files, you could simply write:
$(objects:.o=.c)
# instead of using the general form:
$(patsubst %.o,%.c,$(objects))

Double-Colon Rules ::

  • allow multiple rules to be defined for the same target.
  • If these were single colons, a warning would be printed and only the second set of commands would run.
1
2
3
4
5
6
7
all: blah

blah::
    echo "hello"

blah::
    echo "hello again"

Double pipeline || true

  • It’s the opposite of &&
  • the second command is executed only if the exit status of the preceding command is 0.
1
2
3
4
5
6
7
$ ls this_file_does_not_exist.txt || echo KO
# ls: cannot access this_file_does_not_exist.txt: No such file or directory
# KO

$ ls this_file_exist.txt && echo OK
# this_file_exist.txt
# OK

Commands and execution


Command Echoing/Silencing

Add an @ before a command to stop it from being printed You can also run make with -s to add an @ before each line

1
2
3
all:
    @echo "This make line will not be printed"
    echo "But this will"

Command Execution

  • Each command is run in a new shell (or at least the effect is as such)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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`
# cd ..
# # The cd above does not affect this line
# echo `pwd`
# /Users/luo/Documents/code/maketest
# # This cd command affects the next because they are on the same line
# cd ..; echo `pwd`
# /Users/luo/Documents/code
# # Same as above
# cd ..; \
# 	echo `pwd`
# /Users/luo/Documents/code

Default Shell

  • The default shell is /bin/sh.
  • change this by changing the variable SHELL:
1
2
3
4
SHELL=/bin/bash

cool:
    echo "Hello from bash"

Error handling with -k, -i, and -

  • Add -k
    • when running make to continue running even in the face of errors.
    • Helpful if you want to see all the errors of Make at once.
  • Add a -
    • before a command to suppress the error
  • Add -i
    • to make to have this happen for every command.
1
2
3
4
one:
    # This error will be printed but ignored, and make will continue to run
    -false
    touch one

Interrupting or killing make

Note only: If you ctrl+c make, it will delete the newer targets it just made.


Recursive make

To recursively call a makefile

  1. use the special $(MAKE) instead of make
    • because it will pass the make flags for you and won’t itself be affected by them.
1
2
3
4
5
6
7
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
  1. Use export for recursive make
    • the export directive takes a variable and makes it accessible to sub-make commands.
    • In this example, cooly is exported such that the makefile in subdir can use it.
    • Note: export has the same syntax as sh, but they aren’t related (although similar in function)
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
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)
    @echo "---NewLine---"
# 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

# mkdir -p subdir
# echo "hello:\n\\techo \$(cooly)" | sed -e 's/^ //' > subdir/makefile
# ---MAKEFILE CONTENTS---
# hello:
# 	echo $(cooly)
# ---END MAKEFILE CONTENTS---
# cd subdir && /Applications/Xcode.app/Contents/Developer/usr/bin/make
# echo "The subdirectory can see me!"
# The subdirectory can see me!
# ---NewLine---



# need to export variables to have them run in the shell as well.
one=this will only work locally
export two=we can run subcommands with this
all:
    @echo $(one)
    @echo $$one
    @echo $(two)
    @echo $$two
# this will only work locally
#
# we can run subcommands with this
# we can run subcommands with this

.EXPORT_ALL_VARIABLES exports all variables for you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.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

# mkdir -p subdir
# echo "hello:\n\techo \$(cooly)" | sed -e 's/^ //' > subdir/makefile
# ---MAKEFILE CONTENTS---
# hello:
# 	echo $(cooly)
# ---END MAKEFILE CONTENTS---
# cd subdir && /Applications/Xcode.app/Contents/Developer/usr/bin/make
# echo "The subdirectory can see me!"
# The subdirectory can see me!

Arguments to make

  • a nice list of options that can be run from make. Check out --dry-run, --touch, --old-file.
  • You can have multiple targets to make, i.e. make clean run test runs the clean goal, then run, and then test.

Variables Pt. 2


Flavors of variables

  • recursive =
    • only looks for the variables when the command is used
    • not when it’s defined
  • simply expanded :=
    • like normal imperative programming
    • only those defined so far get expanded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Recursive variable. This will print "later" below
one = one ${later_variable}
later_variable = aloha
# Simply expanded variable. This will not print "later" below
two := two ${later_variable}
later_variable = later
all:
    echo $(one)
    echo $(two)
# echo one later
# one later
# echo two
# two
# echo one later
# one later
# echo two aloha
# two aloha

Simply expanded (using :=) allows you to append to a variable. Recursive definitions will give an infinite loop error.

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

?=

  • only sets variables if they have not yet been set
1
2
3
4
5
6
7
one = hello
one ?= will not be set
two ?= will be set

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

space

  • Spaces at the end of a line are not stripped, but those at the start are.
  • To make a variable with a single space, use $(nullstring)
1
2
3
4
5
6
7
8
9
10
11
with_spaces = hello   # with_spaces has many spaces after "hello"
after = $(with_spaces)there
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
    echo "$(after)"
    echo start"$(space)"end
# echo "hello   there"
# hello   there
# echo start" "end
# start end

An undefined variable is actually an empty string!

1
2
3
all:
    # Undefined variables are just empty strings!
    echo $(nowhere)

Use += to append

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

all:
    echo $(foo)

Command line arguments and override

  • override variables that come from the command line by using override.
  • Here we ran make with make option_one=hi
1
2
3
4
5
6
7
8
9
10
11
# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_one = not_override
all:
    echo $(option_one)
    echo $(option_one)
# echo did_override
# did_override
# echo did_override
# did_override

List of commands and define

  • “define” is actually just a list of commands.
  • It has nothing to do with being a function.
  • Note here that it’s a bit different than having a semi-colon between commands, because each is run in a separate shell, as expected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

# This prints 'I was set'
# I was set!
# This does not print 'I was set' because each command runs in a separate shell
#

Target-specific variables

Variables can be assigned for specific targets

1
2
3
4
5
all: one = cool
all:
    echo one is defined: $(one)
other:
    echo one is nothing: $(one)

Pattern-specific variables

You can assign variables for specific target patterns

1
2
3
4
5
%.c: one = cool
blah.c:
    echo one is defined: $(one)
other:
    echo one is nothing: $(one)

Conditional part of Makefiles


ifeq endif Conditional if/else

1
2
3
4
5
6
7
8
foo = ok

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

$(nullstring) Check if a variable is empty

1
2
3
4
5
6
7
8
9
10
11
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 endif Check if a variable is defined

  • ifdef does not expand variable references;
  • it just sees if something is defined at all
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)

  • test make flags with findstring and MAKEFLAGS.
  • Run this example with make -i to see it print out the echo statement.
1
2
3
4
5
6
7
8
9
10
11
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
# echo "i was passed to MAKEFLAGS"
# i was passed to MAKEFLAGS

Functions


First Functions

Functions

are mainly just for text processing.

  • Call functions with $(fn, arguments) or ${fn, arguments}.
  • make your own using the call builtin function.
  • Make has a decent amount of builtin functions.
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
bar := ${subst not, totally, "I am not superman"}
all:
    @echo $(bar)
# I am  totally superman


# If you want to replace spaces or commas, use variables
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
    @echo $(bar)

# Do NOT include spaces in the arguments after the first.
# That will be seen as part of the string.
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 pattern,replacement, text)

  • The substitution reference $(text:pattern=replacement)
  • replaces only suffixes: $(text:suffix=replacement).
    • No % wildcard is used here.
    • Note: don’t add extra spaces for this shorthand. It will be seen as a search or replacement term.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
foo := a.o b.o l.a c.o
# change %.o to %.c
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
# change %.o to %.c
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, is also equivalent to the above.
three := $(foo:.o=.c)
all:
    echo $(one)
    echo $(two)
    echo $(three)
# echo a.c b.c l.a c.c
# a.c b.c l.a c.c
# echo a.c b.c l.a c.c
# a.c b.c l.a c.c
# echo a.c b.c l.a c.c
# a.c b.c l.a c.c

$(foreach var,list,text)

  • It converts one list of words (separated by spaces) to another.
  • var is set to each word in list, and text is expanded for each word.
  • This appends an exclamation after each word:
1
2
3
4
5
6
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 nonempty, action1 , action2)

  • if checks if the first argument is nonempty.
  • If so runs the second argument, otherwise runs the third.
1
2
3
4
5
6
7
8
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
    @echo $(foo)
    @echo $(bar)
# then!
# else!

The call function

  • Make supports creating basic functions.
  • “define” the function just by creating a variable, but use the parameters $(0), $(1), etc.
  • then call the function with the special call function.
  • The syntax is $(call variable,param,param).
  • $(0) is the variable, while $(1), $(2), etc. are the params.
1
2
3
4
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
    @echo $(call sweet_new_fn, go, tigers)
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"

$(shell ls -la)

  • calls the shell, but it replaces newlines with spaces!
1
2
3
all:
    @echo $(shell ls -la)
# Very ugly because the newlines are gone!

Other Features


include Makefiles

  • tells make to read one or more other makefiles.
  • particularly useful when use compiler flags like -M that create Makefiles based on the source.
  • make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。
1
include filenames...

Example,

  • if some c files includes a header, that header will be added to a Makefile that’s written by gcc.

vpath <pattern> <directories, space/colon separated>

  • Use vpath to specify where some set of prerequisites exist.
  • The format is vpath <pattern> <directories, space/colon separated>
  • <pattern> can have a %, which matches any zero or more characters.
  • can also do this globallyish with the variable VPATH
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

Multiline \

  • use multiple lines when the commands are too long
1
2
3
some_file:
    echo This line is too long, so \
        it is broken up into multiple lines

.phony

  • Adding .PHONY to a target will prevent make from confusing the phony target with a file name.
  • In this example, if the file clean is created, make clean will still be run.
  • .PHONY is great 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

  • The make tool will stop running a rule (and will propagate back to prerequisites) if a command returns a nonzero exit status.
  • DELETE_ON_ERROR will delete the target of a rule if the rule fails in this manner.
  • This will happen for all targets, not just the one it is before like PHONY.
  • It’s a good idea to always use this, even though make does not for historical reasons.
1
2
3
4
5
6
7
8
9
10
.DELETE_ON_ERROR:
all: one two

one:
    touch one
    false

two:
    touch two
    false

Makefile Cookbook

Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)

  • put your C/C++ files in the src/ folder.
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
TARGET_EXEC := final_program

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

# Find all the C and C++ files we want to compile
SRCS := $(shell find $(SRC_DIRS) -name *.cpp -or -name *.c)

# 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)

.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.