入门
为什么会存在 Makefile?
Makefile 用于帮助决定大型程序的哪些部分需要重新编译。在绝大多数情况下,编译 C 或 C++ 文件。其他语言通常有自己的工具,其用途与 Make 相似。当你需要一系列指令来运行取决于哪些文件已更改时,Make 也可以在编译之外使用。本教程将重点介绍 C/C++ 编译用例。
这是你可以使用 Make 构建的示例依赖关系图。如果任何文件的依赖项发生更改,则该文件将被重新编译:
Make 有哪些替代方案?
流行的 C/C++ 替代构建系统是SCons、CMake、Bazel和Ninja。一些代码编辑器(如Microsoft Visual Studio)有自己的内置构建工具。对于 Java,有Ant、Maven和Gradle。Go 和 Rust 等其他语言有自己的构建工具。
Python、Ruby 和 Javascript 等解释型语言不需要类似于 Makefile。Makefiles 的目标是根据已更改的文件来编译需要编译的任何文件。但是当解释语言中的文件发生变化时,不需要重新编译任何东西。当程序运行时,将使用该文件的最新版本。
Make的版本和类型
Make 有多种实现方式,但本指南的大部分内容都适用于你使用的任何版本。但是,它是专门为 GNU Make 编写的,它是 Linux 和 MacOS 上的标准实现。所有示例都适用于 Make 版本 3 和 4,除了一些深奥的差异之外,它们几乎相同。
运行示例
要运行这些示例,你需要一个终端并安装“make”。对于每个示例,将内容放在一个名为 的文件Makefile中,然后在该目录中运行命令make。让我们从最简单的 Makefile 开始:
hello:
echo "Hello, World"
注意:Makefile必须使用TAB而不是空格缩进,否则make会失败。
这是运行上述示例的输出:
$ make
echo "Hello, World"
Hello, World
而已!如果你有点困惑,这里有一个视频,介绍了这些步骤,并描述了 Makefile 的基本结构。
<iframe width="1131" height="703" src="https://www.youtube.com/embed/zeEMISsjO38" title="Beginner Makefile Tutorial" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
生成文件语法
一个 Makefile 由一组规则组成。规则通常如下所示:
targets: prerequisites
command
command
command
- 目标是文件名,以空格分隔。通常,每条规则只有一个。
- 这些命令是通常用于制作目标的一系列步骤。这些需要以制表符开头,而不是空格。
- 先决条件也是文件名,以空格分隔。这些文件需要在运行目标命令之前存在。这些也称为依赖项
Make的精髓
让我们从一个 hello world 示例开始:
hello:
echo "Hello, World"
echo "This line will always print, because the file hello does not exist."
这里已经有很多东西可以吸收了。让我们分解一下:
- 我们有一个目标叫做hello
- 这个目标有两个命令
- 此目标没有先决条件
然后我们将运行make hello
. 只要hello
文件不存在,命令就会运行。如果hello
确实存在,则不会运行任何命令。
重要的是要意识到我说hello
的是target和file。那是因为两者是直接联系在一起的。通常,当运行目标时(也就是运行目标的命令时),这些命令将创建一个与目标同名的文件。在这种情况下,hello
目标不会创建hello
文件。
让我们创建一个更典型的 Makefile - 一个编译单个 C 文件的文件。但在我们这样做之前,请创建一个名为的文件,该文件blah.c
具有以下内容:
// blah.c
int main() { return 0; }
然后创建 Makefile(Makefile一如既往地称为):
blah:
cc blah.c -o blah
这一次,尝试简单地运行make. 由于没有提供目标作为make
命令的参数,因此运行第一个目标。在这种情况下,只有一个目标 (blah
)。第一次运行时,blah
将创建。第二次,你会看到make: 'blah' is up to date
。那是因为该blah
文件已经存在。但是有一个问题:如果我们修改blah.c
然后运行make
,则不会重新编译。
我们通过添加一个先决条件来解决这个问题:
blah: blah.c
cc blah.c -o blah
当我们make再次运行时,会发生以下一组步骤:
- 选择第一个目标,因为第一个目标是默认目标
- 这有一个先决条件
blah.c
- Make 决定它是否应该运行
blah
目标。它只会在blah
不存在或更新blah.c
时运行blah
最后一步很关键,也是make 的精髓。它试图做的是确定blah
自blah
上次编译以来是否发生了先决条件。即如果blah.c
被修改,运行make应该重新编译文件。反之,如果blah.c
没有改变,则不应重新编译。
为了实现这一点,它使用文件系统时间戳作为代理来确定是否发生了变化。这是一个合理的启发式方法,因为文件时间戳通常只有在文件被修改时才会改变。但重要的是要意识到情况并非总是如此。例如,你可以修改一个文件,然后将该文件的修改时间戳更改为旧的。如果你这样做了,Make 会错误地猜测文件没有更改,因此可能会被忽略。
多嘴一句: 确保你理解这一点。它是 Makefile 的关键,可能需要你几分钟才能正确理解.如果事情仍然令人困惑,请尝试上述示例或观看上面的视频。
更多快速示例
以下 Makefile 最终运行所有三个目标。当你make在终端中运行时,它将构建一个程序blah,通过一系列步骤调用:
- Make 选择目标blah,因为第一个目标是默认目标
- blah需要blah.o,所以搜索blah.o目标
- blah.o需要blah.c,所以搜索blah.c目标
- blah.c没有依赖关系,所以echo命令运行
- 然后运行该cc -c命令,因为所有blah.o依赖项都已完成
- 运行topcc命令,因为所有blah依赖都完成了
- 就是这样:blah是一个编译的c程序
blah: blah.o
cc blah.o -o blah # Runs third
blah.o: blah.c
cc -c blah.c -o blah.o # Runs second
# Typically blah.c would already exist, but I want to limit any additional required files
blah.c:
echo "int main() { return 0; }" > blah.c # Runs first
如果你delete blah.c
,所有三个目标都将重新运行。如果你编辑它(从而将时间戳更改为比blah.o
更新),前两个目标将运行。如果你运行touch blah.o(因此将时间戳更改为 blah更新),那么只有第一个目标会运行。如果你什么都不做,那么目标都不会运行。试试看!
下一个示例没有做任何新的事情,但仍然是一个很好的附加示例。它将始终运行两个目标,因为some_file依赖于other_file,它永远不会被创建。
some_file: other_file
echo "This will always run, and runs second"
touch some_file
other_file:
echo "This will always run, and runs first"
Make clean
clean
常被用作去除其他目标输出的目标,但在Make中并不是一个特殊的词。你可以运行make
和make clean
在此创建和删除some_file.
请注意,clean这里做了两个新的事情:
- 它不是第一个目标(默认),也不是先决条件。这意味着它永远不会运行,除非你明确调用
make clean
- 它不是一个文件名。如果你碰巧有一个名为 的文件clean,这个目标将不会运行,这不是我们想要的。请参阅.PHONY本教程后面的有关如何解决此问题的信息
some_file:
touch some_file
clean:
rm -f some_file
变量
变量只能是字符串。你通常会想要使用:=
,但=
也可以使用。见变量Pt 2。
下面是一个使用变量的例子:
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
单引号或双引号对 Make 没有意义。它们只是分配给变量的字符。但是,引号对shell/bash 很有用,你需要在printf. 在此示例中,两个命令的行为相同:
a := one two # a is assigned to the string "one two"
b := 'one two' # Not recommended. b is assigned to the string "'one two'"
all:
printf '$a'
printf $b
使用${}
或引用变量$()
x := dude
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x
目标
所有目标
制作多个目标并且你希望所有目标都运行?做一个all
目标。make由于这是列出的第一条规则,如果在没有指定目标的情况下调用它,它将默认运行。
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
多个目标
当一个规则有多个目标时,将为每个目标运行命令。$@
是一个包含目标名称的自动变量。
all: f1.o f2.o
f1.o f2.o:
echo $@
# Equivalent to:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o
自动变量和通配符
*
通配符
*
和%
在 Make 中都称为通配符,但它们的含义完全不同。*
在你的文件系统中搜索匹配的文件名。我建议你始终将其包装在wildcard函数中,否则你可能会陷入下面描述的常见陷阱。
# Print out file information about every .c file
print: $(wildcard *.c)
ls -la $?
*
可以在目标、先决条件或wildcard函数中使用。
危险:*
不能在变量定义中直接使用
危险:当*
没有匹配到文件时,保持原样(除非在wildcard函数中运行)
thing_wrong := *.o # Don't do this! '*' will not get expanded
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)
%
通配符
%
确实很有用,但是由于可以使用的情况多种多样,因此有些混乱。
- 在“匹配”模式下使用时,它匹配字符串中的一个或多个字符。这种匹配称为茎。
- 在“替换”模式下使用时,它采用匹配的词干并替换字符串中的词干。
%
最常用于规则定义和某些特定功能中。
有关使用它的示例,请参阅以下部分:
自动变量
有许多自动变量,但通常只显示几个:
hey: one two
# Outputs "hey", since this is the target name
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
花式规则
隐式规则
make 喜欢 c 编译。每次它表达爱意时,事情都会变得混乱。Make 中最令人困惑的部分可能是制定的魔法/自动规则。调用这些“隐式”规则。我个人不同意这个设计决定,也不推荐使用它们,但它们经常被使用,因此了解它们很有用。以下是隐式规则列表:
- 编译 C 程序:使用以下形式的命令n.o自动生成n.c
$(CC) -c $(CPPFLAGS) $(CFLAGS)
- 编译 C++ 程序:n.o由n.cc或n.cpp使用以下形式的命令自动生成
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
- 链接单个目标文件:通过运行命令n自动生成n.o
$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
隐式规则使用的重要变量是:
CC
:用于编译C程序的程序;默认ccCXX
: 用于编译 C++ 程序的程序;默认g++CFLAGS
: 提供给 C 编译器的额外标志CXXFLAGS
: 提供给 C++ 编译器的额外标志CPPFLAGS
: 给 C 预处理器的额外标志LDFLAGS
: 当编译器应该调用链接器时提供额外的标志
让我们看看我们现在如何构建一个 C 程序,而无需明确告诉 Make 如何进行编译:
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*
静态模式规则
静态模式规则是另一种在 Makefile 中少写的方法,但我想说它更有用,也少了一点"魔力"。这是他们的语法:
targets...: target-pattern: prereq-patterns ...
commands
本质是target_parttern
来匹配给定的target
。匹配的内容称为stem。然后将stem根据prereq-patter
进行组合以生成目标的先决条件。
一个典型的用例是将文件编译.c成.o文件。这是手动方式:
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
这是更有效的方法,使用静态模式规则:
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
静态模式规则和过滤器
当我稍后介绍函数时,我将预示你可以用它们做什么。该filter函数可用于静态模式规则以匹配正确的文件。在这个例子中,我组成了.raw和.result扩展。
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)
模式规则
模式规则经常被使用但相当混乱。你可以将它们视为两种方式:
- 一种定义自己的隐式规则的方法
- 一种更简单的静态模式规则
让我们先从一个例子开始:
# 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 $@
双冒号规则
双冒号规则很少使用,但允许为同一个目标定义多个规则。如果这些是单冒号,则会打印一条警告,并且只会运行第二组命令。
all: blah
blah::
echo "hello"
blah::
echo "hello again"
命令和执行
命令回显/静音
在命令之前添加一个@
以阻止它被打印
你也可以用make -s
运行,这样相当于在每行之前添加一个@
all:
@echo "This make line will not be printed"
echo "But this will"
命令执行
每个命令都在一个新的 shell 中运行(或者至少效果是这样的)
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
来更改它:
SHELL=/bin/bash
cool:
echo "Hello from bash"
$$
如果你想让一个字符串有一个美元符号,你可以使用$$
. 这是如何在bash或sh中使用 shell变量的方法。
请注意下一个示例中 Makefile 变量和 Shell 变量之间的区别。
make_var = I am a make variable
all:
# Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
sh_var='I am a shell variable'; echo $$sh_var
# Same as running "echo I am a amke variable" in the shell
echo $(make_var)
-k
、-i
和-
的错误处理
- 在运行 make 时添加
-k
,即使遇到错误也能继续运行。如果你想一次查看 Make 的所有错误,这很有帮助。 - 在命令前添加一个
-
以抑制错误 - 添加
-i
以使每个命令都发生这种情况。
one:
# This error will be printed but ignored, and make will continue to run
-false
touch one
打断或中止make
仅注意:如果你<keyboard>ctrl+c</keyboard>make
,它将删除它刚刚删除你最新生成的目标。
递归使用make
要递归调用 makefile,请使用 $(MAKE)
而不是make
,因为它会为你传递 make flags, 并且本身不会受到它们的影响。
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 变量。
# Run this with "export shell_env_var='I am an environment variable'; make"
all:
# Print out the Shell variable
echo $$shell_env_var
# Print out the Make variable
echo $(shell_env_var)
该export指令接受一个变量并将其设置为所有配方中所有 shell 命令的环境:
shell_env_var=Shell env var, created inside of Make
export shell_env_var
all:
echo $(shell_env_var)
echo $$shell_env_var
因此,当你在 make 中运行make命令时,你可以使用该export指令使其可供子 make 命令访问。在这个例子中,cooly被导出以便 subdir 中的 makefile 可以使用它。
new_contents = "hello:\n\techo \$$(cooly)"
all:
mkdir -p subdir
printf $(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 中运行。
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:
new_contents = "hello:\n\techo \$$(cooly)"
cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly
all:
mkdir -p subdir
printf $(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选项
有一个很好的选项列表。看看--dry-run, --touch, --old-file.
你可以一次运行多个目标,即make clean run test
, 运行clean目标,然后run,然后test。
变量2
风味和修饰
有两种类型的变量:
- 递归 (
=
) - 仅在使用命令时查找变量,而不是在定义时查找变量。 - 简单地扩展(
:=
)——就像普通的命令式编程, 只有那些到目前为止定义的变量被扩展
# Recursive variable. This will print "later" below
one = one ${later_variable}
# Simply expanded variable. This will not print "later" below
two := two ${later_variable}
later_variable = later
all:
echo $(one)
echo $(two)
简单扩展(使用:=)允许你附加到变量。递归定义将产生无限循环错误。
one = hello
# one gets defined as a simply expanded variable (:=) and thus can handle appending
one := ${one} there
all:
echo $(one)
?=
仅在尚未设置变量时设置变量
one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)
行尾的空格不会被删除,但开头的空格会被删除。要使用单个空格创建变量,请使用$(nullstring)
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
未定义的变量实际上是一个空字符串!
all:
# Undefined variables are just empty strings!
echo $(nowhere)
用于+=追加
foo := start
foo += more
all:
echo $(foo)
字符串替换也是一种非常常见且有用的修改变量的方法。另请查看Text Functions和Filename Functions。
命令行参数和覆盖
你可以使用override
覆盖来自命令行的变量。在这里,我们运行make option_one=hi
# 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
指示符不是一个函数,尽管它看起来可能是这样的。我见过它使用得很少,所以我不会详细介绍,但它主要用于定义[canned recipes](https://www.gnu.org/software/make/manual/html_node/Canned-Recipes.html#Canned-Recipes)
,并且与[eval](https://www.gnu.org/software/make/manual/html_node/Eval-Function.html#Eval-Function)
函数配合得很好。
define/endef
简单只是创建一个代表一系列命令的变量。请注意,这与在命令之间使用分号有点不同,因为define里每个命令都在单独的 shell 中运行,正如预期的那样。
one = export blah="I was set!"; echo $$blah
define two
export blah="I was set!"
echo $$blah
endef
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)
和目标绑定的特定变量
可以为特定目标分配变量
all: one = cool
all:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
和模式绑定的变量
你可以为特定目标模式分配变量
%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
Makefile 的条件
if/else
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
检查变量是否为空
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
不扩展变量引用;它只是查看是否定义了某些东西
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "but bar is not"
endif
$(makeflags)
此示例向你展示如何使用findstring和测试 make 标志MAKEFLAGS。运行此示例make -i以查看它打印出 echo 语句。
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
函数
第一个函数
函数主要只是用于文本处理。通过$(fn, arguments)
或${fn, arguments}
调用。你可以使用call
调用自己创建的函数。Make 有相当数量的内置函数。
bar := ${subst not, totally, "I am not superman"}
all:
@echo $(bar)
如果要替换空格或逗号,使用变量
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)
不要在第一个参数之后包含空格。这将被视为字符串的一部分。
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)
执行以下操作:
“在文本中查找与pattern
匹配的空格分隔的单词并用replacement
替换它们。这里的pattern
可能包含一个充当通配符的'%',匹配一个单词中任意数量的任何字符。如果replacement
还包含一个'%', '%' 被替换为与模式中的 '%' 匹配的文本。只有pattern
和replacement
中的第一个 '%' 会以这种方式处理;任何后续的 '%' 都不会改变。(GNU 文档)
替换引用$(text:pattern=replacement)
是对此的简写。
还有另一种只替换后缀的速记:$(text:suffix=replacement)
. 这里不允许 使用%通配符。
注意:不要为这个速记添加额外的空格。它将被视为搜索词或替换词。
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中依次进行扩展。
下面的例子会在每个单词后附加一个感叹号:
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函数
if
检查第一个参数是否为非空。如果是,则运行第二个参数,否则运行第三个。
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)
,$(2)
等是参数。
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,但它用空格替换了换行符!
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
其它功能
Include Makefile
include 指令告诉 make 读取一个或多个其他 makefile。它是 makefile makefile 中的一行,如下所示:
include filenames...
当你使用-M
编译器标志(如基于源代码创建 Makefile )时,这特别有用。例如,如果某些 c 文件包含头文件,则该头文件将被添加到由 gcc 编写的 Makefile 中。我在Makefile Cookbook中有更多的讨论
vpath指令
使用 vpath 指定某些先决条件存在的位置。格式vpath <pattern> <directories, space/colon separated>
<pattern>
可以有一个`%,它匹配任何零个或多个字符。
你也可以使用全局变量 VPATH 执行此操作
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
多行
当命令太长时,反斜杠(“\”)字符使我们能够使用多行
some_file:
echo This line is too long, so \
it is broken up into multiple lines
.phony
添加.PHONY到目标将防止 Make 将虚假目标与文件名混淆。
在此示例中,如果clean创建了文件,make clean
仍将运行。从技术上讲,我应该在每个示例中都使用all
和clean
,但我并没有保持示例的简洁。此外,"phony"目标通常都不是文件名的名称.
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
.delete_on_error
如果命令返回非零退出状态,make 工具将停止运行规则(并将传播回先决条件)。
如果规则以这种方式失败,DELETE_ON_ERROR
将删除目标。这将发生在所有目标上,而不仅仅是PHONY目标。使用它始终是一个好主意,即使 make 出于历史原因没有这样做。
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false
Makefile Cookbook
让我们浏览一个非常丰富的 Make 示例,它适用于中型项目。
这个 makefile 的巧妙之处在于它会自动为你确定依赖关系。你所要做的就是将你的 C/C++ 文件放入该src/文件夹中。
# Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
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)
$(CXX) $(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)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。