时间轴

2025-11-01

init


参考文档:

还有一篇翻译的也不错:

Makefile 简介

​ Makefiles 用于帮助决定一个大型程序的哪些部分需要重新编译。在绝大多数情况下,需要编译的只是 C 或 C++ 文件。其他语言通常有它们自己的一套与 Make 用途类似的工具。Make 的用途并不局限于编程

​ 除了 Make,还有一些比较流行的构建系统可选,像 SCon 、CMake 、Bazel 和 Ninja 等。一些代码编辑器,像 Microsoft Visual Studio ,内置了它们自己的构建工具。Java 语言的构建工具有 Ant 、Maven 和 Gradle 可选,其他语言像 Go 和 Rust 则都有它们自己的构建工具。
​ 像 Python、Ruby 和 JavaScript 这样的解释型语言是不需要类似 Makefiles 的东西的。Makefiles 的目标是基于哪些文件发生了变化来编译需要被编译的一切文件。但是,当解释型语言的文件发生了变化,是不需要重新编译的,程序运行时会使用最新版的源码文件

Makefile语法

Makefile 文件由一系列的 规则 (rules) 组成,一个规则类似下面这样:

1
2
3
4
targets: prerequisites
command
command
command
  • targets 指的是文件名称,多个文件名以空格分隔。通常,一个规则只对应一个文件。
  • commands 通常是一系列用于制作(make)一个或多个目标(targets)的步骤。它们 需要以一个制表符开头,而不是空格。
  • prerequisites 也是文件名称,多个文件名以空格分隔。在运行目标(targets)的 commands 之前,要确保这些文件是存在的。它们也被称为 依赖

例子:

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

-c 选项只编译不链接,-o file 将其前面命令的输出内容放在文件 file 中。

Targets

依赖

例子:

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

目标 some_file 依赖 other_file。当我们运行 make 时,默认目标(即 some_file,因为它是第一个)的构建会被调用。构建系统首先查看目标的 依赖 列表,若其中有旧的目标文件,构建系统首先会为这些依赖执行目标构建,此后才轮到默认目标。第二次执行 make 时,默认目标和依赖目标下的命令都不会再运行了,因为二者都存在了。

伪目标或虚拟目标

1
2
3
4
5
some_file: other_file
touch some_file

other_file:
echo "nothing"

类似上面 other_file 这样的目标就是俗称的 伪目标虚拟目标

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

执行make命令默认执行的targets

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 $@

当一个规则(rule)有多个目标时,那么对于每个目标,这个规则下面的 commands 都会运行一次。

$@ 是一个指代目标名称的自动变量(Automatic Variables)。

Automatic Variables

通配符*

在 Make 中,%* 都叫作通配符,但是它们是两个完全不同的东西。* 会搜索你的文件系统来匹配文件名

建议应该一直使用 wildcard 函数来包裹它,要不然可能会掉入陷阱中。

不用 wildcard 包裹的 * 除了能给人徒增迷惑,毫无可取之处。

1
2
3
# 打印出每个.c文件的文件信息
print: $(wildcard *.c)
ls -la $?

* 不能直接用在变量定义中。

* 匹配不到文件时,它将保持原样(除非被 wildcard 函数包裹)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
thing_wrong := *.o # 请不要这样做!'*.o' 将不会被替换为实际的文件名
thing_right := $(wildcard *.o)

all: one two three four

# 失败,因为$(thing_wrong)是字符串"*.o"
one: $(thing_wrong)

# 如果没有符合这个匹配规则的文件,它将保持为 *.o :(
two: *.o

# 按预期运行!在这种情况下,什么都不会执行
three: $(thing_right)

# 与规则三相同
four: $(wildcard *.o)

通配符%

  • 在“匹配”模式下使用时,它匹配字符串中的一个或多个字符,这种匹配被称为词干(stem)匹配。
  • 在“替换”模式下使用时,它会替换匹配到的词干。
  • % 大多用在规则定义以及一些特定函数中。

自动变量

automatic variables

虽然有很多自动变量,但常用的没有几个

$@

当前规则的目标文件名 (target)

$?

当前规则中比目标文件新的依赖文件列表

例子:对于规则:foo: a.c b.c,若 b.c 更新 → $?b.c

$<

当前规则的第一个依赖文件 (prerequisite)

$^

当前规则的所有依赖文件(去重)

Fancy Rules

隐式规则

Make 钟爱 C 编译,它每次表达爱意时,都会做出一些“迷惑行为”。其中最令人迷惑的部分可能就是它的那些魔法般的规则了,Make 称之为“隐式规则”。并不推荐使用。

下面列出了隐式规则:

  • 编译 C 程序时:使用 $(CC) -c $(CPPFLAGS) $(CFLAGS) 形式的命令,n.o 会由 n.c 自动生成。
  • 编译 C++ 程序时:使用 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) 形式的命令,n.o 会由 n.ccn.pp 自动生成。
  • 链接单个目标文件时:通过运行 $(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS) 命令,n 会由 n.o 自动生成。

上述隐式规则使用的变量的含义如下所示:

  • CC:编译 C 程序的程序,默认是 cc
  • CXX:编译 C++ 程序的程序,默认是 g++
  • CFLAGS:提供给 C 编译器的额外标志
  • CXXFLAGS:提供给 C++ 编译器的额外标志
  • CPPFLAGS:提供给 C 预处理器的额外标志
  • LDFLAGS:当编译器应该调用链接器时提供给编译器的额外标志

隐式规则的例子:

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*

静态模式规则

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

它的本质是:给定的目标 targettarget-patterntargets 中匹配得到(利用通配符 %。匹配到的内容被称为 词干 (stem)。然后,将词干替换到 prereq-pattern 中去,并以此生成目标的 prerequisites 部分

静态模式规则的一个典型用例就是把 .c 文件编译为 .o 文件。

手动 方式:

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

静态模式 方式:

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

解释:

1
$(objects): %.o: %.c

这行很关键,它的意思是:

对于 $(objects)(即 foo.o、bar.o、all.o),如果有一个匹配规则 %.o,则依赖于对应的 %.c 文件。

例如:

  • foo.o 依赖 foo.c
  • bar.o 依赖 bar.c
  • all.o 依赖 all.c

这其实等价于:

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

但是用一行自动生成,体现了 通配符规则的强大性

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

%.c:
touch $@

make 发现 foo.o 需要 foo.c 而不存在时,
它会触发 %.c: 规则来创建空文件 foo.c

make 需要 all.o 时,会触发 all.c: 规则(生成有内容的文件)

也可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义目标文件列表
objects = foo.o bar.o all.o

# 最终目标
all: $(objects)
$(CC) -o all $(objects)

# 通用编译规则:任何 .o 都由同名 .c 编译而来
%.o: %.c
$(CC) -c $< -o $@

# 生成 all.c(如果不存在)
all.c:
echo "int main() { return 0; }" > all.c

# 若没有对应的 .c 文件,自动创建一个空文件
%.c:
touch $@

# 清理
clean:
rm -f *.c *.o all

静态模式规则与过滤器

例子:

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)

声明伪目标

1
.PHONY: all clean
  • 表示 allclean 不是实际文件,而是逻辑命令
  • 避免因为存在名为 all 的文件而导致 make 混淆。

主目标(入口)

1
all: $(obj_files)
  • 默认目标 all 依赖于所有 obj_files

  • 当执行 make 时,Make 会去构建:

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

filter 筛选不同类型文件的规则

.o 文件的规则:

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

解释:

  • $(filter %.o,$(obj_files))
    → 从 obj_files 里筛选出 .o 结尾的文件:

    1
    bar.o lose.o
  • 展开后等价于:

    1
    2
    bar.o lose.o: %.o: %.c
    @echo "target: $@ prereq: $<"
  • %.o: %.c静态模式规则

    • 目标形如 xxx.o,依赖于同名的 xxx.c
    • $@:表示目标文件名(例如 bar.o
    • $<:表示第一个依赖文件(例如 bar.c

这条规则不是真的编译,只是打印信息:

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

.result 文件的规则:

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

同理:

  • $(filter %.result,$(obj_files)) → 结果是 foo.result

  • 展开后等价于:

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

意思是:要生成 foo.result,需要 foo.raw

模式规则

一个例子:

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

模式规则在目标中包含了一个 %,这个 % 匹配任意非空字符串,其他字符匹配它们自己。一个模式规则的 prerequisite 中的 % 表示目标中 % 匹配到的同一个词干。

另一个例子:

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 $@

双冒号规则

1
2
target :: prerequisites
commands

含义

  • 目标可以被定义多次,每个规则都独立存在。
  • 只要有一个规则的依赖文件更新,该规则的命令就会单独执行。

例子

1
2
3
4
5
foo :: a
echo "rule 1 triggered by a"

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

执行:

1
$ make foo

假设:

  • afoo 新(被更新)
  • bfoo 旧(未更新)

输出结果:

1
rule 1 triggered by a

如果 ab 都比 foo 新,则输出:

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

命令与执行

回显/静默命令

在一个命令前添加一个 @ 符号就会阻止该命令输出内容。

你也可以使用 make -s 在每个命令前添加 @

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

命令的执行

每个命令都运行在一个新的 shell 中(或者说运行效果等同于运行在一个新 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`

更改默认Shell

系统默认的 shell 是 /bin/sh,可以通过改变 SHELL 变量的值来改变它:

1
2
3
4
SHELL=/bin/bash

cool:
echo "Hello from bash"

错误处理:-k-i-

make -k 会使得即便遇到错误,构建也会继续执行下去。经常用于一次查看 Make 的所有错误。

在一个命令前添加 - 会抑制错误

make -i 等同于在每个命令前添加 -

1
2
3
4
one:
# This error will be printed but ignored, and make will continue to run
-false
touch one

中断或杀死 make

make 的过程中,使用了 ctrl+c,那么刚刚制作的新目标会被删除。

make 的递归用法

为了递归应用一个 makefile,要使用 $(MAKE) 而不是 make,因为它会传递构建标志,而使用了 $(MAKE) 变量的这一行命令不会应用这些标志。

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
写法 行为 推荐
make 普通命令,不继承 make 的选项
$(MAKE) 特殊变量,表示“当前 make 程序”本身,会自动继承选项

环境变量

export

指令 export 携带了一个变量,并且对子 make 命令可见

在下面的例子中,变量 cooly 被导出以便在子目录中的 makefile 可以使用它。

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

向shell传递变量

向shell传递变量也要export导出

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可以导出所有变量。

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

make 传递参数

选项 / 用法 全写命令 功能说明 示例
-n / --dry-run make --dry-run 只显示将要执行的命令,但不实际运行。用于调试 Makefile。 make --dry-run all(查看执行流程)
-t / --touch make --touch 将目标文件标记为“最新”(更新时间戳),但不真正执行命令。常用于跳过实际构建。 make --touch all
-o <file> / --old-file=<file> make --old-file=foo.o 指定某个文件视为“旧文件”,使依赖于它的目标不会被重建。 make --old-file=main.o

可以同时传递多个目标给 make,例如 make clean run test 会先后运行 cleanruntest

变量

变量的类型

  • 递归变量(使用 =)- 只有在命令执行时才查找变量,而不是在定义时
  • 简单的扩展变量(使用 :=)- 就像普通的命令式编程一样——只有当前已经定义的变量才会得到扩展

例子:

1
2
3
4
5
6
7
8
9
10
# Recursive variable. 会打印出later
one = one ${later_variable}
# Simply expanded variable. 不会打印出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)
  • ?=

当变量还没被设置值时给它设置值,反之则忽略。

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

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

用来追加变量的值:

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

all:
echo $(foo)

命令行变量

可以通过 override 来覆盖来自命令行的变量。

假使我们使用下面的 makefile 执行了这样一条命令 make option_one=hi,那么变量 option_one 的值就会被覆盖掉。

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 定义命令列表

它与函数 define 没有任何关系。

例子:

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)

这里请注意,它与用分号分隔多个命令的场景有点不同,因为前者如预期的那样,每条命令都是在一个单独的 shell 中运行的。

即:每一行命令(在 recipe 中)都是在一个新的 独立 shell 实例 中执行的。所以环境变量或当前目录等修改不会自动延续到下一行。

例如:

1
2
3
all:
export FOO=bar
echo $$FOO

不会打印任何东西,因为:

  • 第1行在一个 shell 中运行,设置了环境变量。
  • 第2行在另一个 shell 中运行,看不到前一个 shell 的环境。

特定目标的变量

我们可以为特定目标分配变量。

1
2
3
4
5
6
7
all: one = cool

all:
echo one is defined: $(one)

other:
echo one is nothing: $(one)

特定模式的变量

我们可以为特定的目标 模式 分配变量。

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

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

other:
echo one is nothing: $(one)

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 检查变量是否为空

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检查变量是否定义

ifdef 不会扩展变量引用,它只会查看变量的内容是否定义。

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)

MAKEFLAGSGNU Make 的内置变量,它会自动保存当前 make 的所有命令行选项。

调用方式 MAKEFLAGS 的内容
make (空)
make -i i
make -k k
make -ik ik
make -n -s ns
make -j4 j4(含数字参数)
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))

  • 这是 GNU Make 内置函数之一。
  • 功能:在第二个字符串中查找第一个字符串。
  • 如果找到,就返回第一个字符串(这里是 "i");
  • 如果没找到,就返回空字符串。

ifneq (,$(...))

  • 语法:ifneq (arg1, arg2)
    表示“如果 arg1 和 arg2 不相等”则执行后续语句。
  • 这里写成 (, $(findstring ...))
    意思是 “如果 findstring 的结果 非空”。

因此整句逻辑等价于:

如果 MAKEFLAGS 中包含字母 i,则执行 echo。

函数

函数 主要用于文本处理。函数调用的语法是 $(fn, arguments)${fn, arguments}。你可以使用内置的函数 call 来制作自己的函数。Make 拥有数量众多的 内置函数 。

1
2
3
bar := ${subst not, totally, "I am not superman"}
all:
@echo $(bar)

如果想替换空格或逗号,需要使用变量:

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)

不要 在第一个参数之后的参数中包含空格,这将被视为字符串的一部分。

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 字符串替换

$(patsubst pattern,replacement,text) 做了下面这些事:

“在文本中查找匹配的以空格分隔的单词,用 replacement 替换它们。这里的 pattern 可以包含一个 % 作为通配符以匹配单词中任意数量的任意字符。如果 replacement 中也包含了一个 %,那它表示的内容将被 pattern 中的 % 匹配的内容替换。只有 patternreplacement 中的第一个 % 才会采取这种行为,随后的任何 % 都将保持不变。

$(text:pattern=replacement) 是一个简化写法。

还有一个仅替换后缀的简写形式:$(text:suffix=replacement),这里没有使用通配符 %

注意: 在简写形式中,不要添加额外的空格,它会被当作一个搜索或替换项。

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

函数 foreach 看起来像这样:$(foreach var,list,text),它用于将一个单词列表(空格分隔)转换为另一个

  • var 表示循环中的每一个单词
  • list 表示要循环的变量

  • text 用于扩展每个单词。

例子:在每个单词后追加一个感叹号:

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)

if 函数用来检查它的第 1 个参数是否非空如果非空,则运行第 2 个参数,否则运行第 3 个

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 支持创建基本的函数。你只需通过创建变量来“定义”函数,只是会用到参数 $(0)$(1) 等。然后,你就可以使用专门的函数 call 来调用它了,语法是 $(call variable,param,param)$(0) 是变量名,而 $(1)$(1) 等则是参数。

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 - 调用 shell,但它在输出中会用空格替代换行。

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

其他特性

include

include 指令告诉 make 去读取其他 makefiles 文件,它是 makefile 中的一行,如下所示:

1
include filenames...

vpath

vpath 指令用来指定某些 prerequisites 的位置,使用格式是 vpath <pattern> <directories, space/colon separated>

vpath 告诉 Make:当依赖文件不在当前目录时,应该到哪些目录去找

<pattern> 中可以使用 %,用来匹配 0 个或多个字符。

你也可以使用变量 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
1
vpath %.h ../headers ../other-directory

含义:对所有以 .h 结尾的文件(即头文件),如果当前目录找不到,就依次到../headers../other-directory去搜索。

多行处理

当命令过长时,反斜杠(\)可以让我们使用多行编写形式。

1
2
3
some_file: 
echo This line is too long, so \
it is broken up into multiple lines

.PHONY

向一个目标中添加 .PHONY避免把一个虚拟目标识别为一个文件名

在下面这个例子中,即便文件 clean 被创建了,make clean 仍会运行。.PHONY 非常好用。

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

如果一个命令返回了一个非 0 的退出码,那么 make 会停止运行相应的规则(并会传播到它的依赖中)。如果一个规则因为上述这种情况构建失败了,那么应用了 .DELETE_ON_ERROR 后,这个规则的目标文件就会被删除。

不像 .PHONY.DELETE_ON_ERROR 对所有的目标都有效。始终使用 .DELETE_ON_ERROR 是个不错的选择,即使由于历史原因,make 不支持它。

1
2
3
4
5
6
7
8
9
10
.DELETE_ON_ERROR:
all: one two

one:
touch one
false

two:
touch two
false

例子

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)