Make Tutorial
hello:
echo "Hello, World"
Makefile Syntax
targets: prerequisites
command
command
command
目标是文件名,以空格分隔。通常,每条规则只有一个。
这些命令是通常用于创建目标的一系列步骤。这些需要以制表符开头,而不是空格。
先决条件也是文件名,以空格分隔。在运行目标的命令之前,这些文件需要存在。这些也称为依赖项
- Make选择目标blah,因为第一个目标是默认目标
- blah 需要 blah.o,因此搜索 blah.o 目标
- blah.o 需要 blah.c,因此搜索 blah.c 目标
- blah.c 没有依赖项,因此运行 echo 命令
- 然后运行 cc -c 命令,因为所有 blah.o 依赖项都已完成
- 运行top cc命令,因为所有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
// blah.c
int main() { return 0; }
如果删除 blah.c,所有三个目标都将重新运行。如果您对其进行编辑(从而将时间戳更改为比 blah.o 新),则前两个目标将运行。如果您运行 touch blah.o (从而将时间戳更改为比 blah 更新),则只有第一个目标会运行。如果您不进行任何更改,则所有目标都不会运行。试试看!
下一个示例没有做任何新的事情,但仍然是一个很好的附加示例。它将始终运行两个目标,因为 some_file 依赖于 other_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
它不是首要目标(默认目标),也不是先决条件。这意味着除非您显式调用 make clean,否则它永远不会运行
它并不是一个文件名。如果您碰巧有一个名为 clean 的文件,则该目标将不会运行,这不是我们想要的。
some_file:
touch some_file
clean:
rm -f some_file
Variables
变量只能是字符串。您通常需要使用 :=,但 = 也可以。
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 set to the string "one two"
b := 'one two' # Not recommended. b is set to the string "'one two'"
all:
printf '$a'
printf $b
x := dude
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x
Targets
The all target
Making multiple targets and you want all of them to run? Make an all
target. Since this is the first rule listed, it will run by default if make
is called without specifying a target.
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
Multiple targets
当规则有多个目标时,将为每个目标运行命令。 $@ 是包含目标名称的自动变量。
all: f1.o f2.o
f1.o f2.o:
echo $@
# Equivalent to:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o
Automatic Variables and Wildcards
* Wildcard
- 和 % 在 Make 中都称为通配符,但它们的含义完全不同。 * 在您的文件系统中搜索匹配的文件名。我建议您始终将其包装在通配符函数中,因为否则您可能会陷入下面描述的常见陷阱。
# Print out file information about every .c file
print: $(wildcard *.c)
ls -la $?
‘*’ 可以用在目标、先决条件或通配符函数中。
危险: * 不能直接用在变量定义中
危险:当 * 不匹配任何文件时,保持原样(除非在通配符函数中运行)
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)
% Wildcard
- % 确实很有用,但由于它可以在多种情况下使用而有点令人困惑。
- 当用于“匹配”模式时,它匹配字符串中的一个或多个字符。这场比赛被称为“茎”。
- 当在“替换”模式下使用时,它会采用匹配的词干并替换字符串中的词干。
- % 最常用于规则定义和一些特定函数中。
Automatic Variables
自动变量有很多,但通常只出现几个:
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
Fancy Rules
Implicit Rules
Make喜欢c编译。每次它表达爱意时,事情都会变得混乱。也许 Make 最令人困惑的部分是所制定的神奇/自动规则。调用这些“隐式”规则。我个人并不同意这种设计决策,也不建议使用它们,但它们经常被使用,因此了解它们很有用。以下是隐含规则的列表:
-
编译 C 程序:n.o 是通过 $(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@ 形式的命令从 n.c 自动生成的
-
编译 C++ 程序:n.o 是通过 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@ 形式的命令从 n.cc 或 n.cpp 自动生成的
-
链接单个目标文件:通过运行命令 $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ 从 n.o 自动生成 n
隐式规则使用的重要变量是:
-
CC:编译C程序的程序;默认抄送
-
CXX:编译C++程序的程序;默认g++
-
CFLAGS:提供给 C 编译器的额外标志
-
CXXFLAGS:提供给 C++ 编译器的额外标志
-
CPPFLAGS:提供给 C 预处理器的额外标志
-
LDFLAGS:当编译器应该调用链接器时提供给编译器的额外标志
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
静态模式规则是另一种在 Makefile 中减少编写的方法,但我想说它更有用,而且不那么“神奇”。这是他们的语法:
targets...: target-pattern: prereq-patterns ...
commands
本质是给定的目标与目标模式匹配(通过 % 通配符)。匹配的任何东西都称为茎。然后将主干替换为先决条件模式,以生成目标的先决条件。
一个典型的用例是将 .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
Static Pattern Rules and Filter
当我稍后介绍函数时,我将预示您可以使用它们做什么。过滤功能可以用在静态模式规则中来匹配正确的文件。在此示例中,我编写了 .raw 和 .result 扩展名。
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c
all: $(obj_files)
# Note: PHONY is important here. Without it, implicit rules will try to build the executable "all", since the prereqs are ".o" files.
.PHONY: all
# Ex 1: .o files depend on .c files. Though we don't actually make the .o file.
$(filter %.o,$(obj_files)): %.o: %.c
echo "target: $@ prereq: $<"
# Ex 2: .result files depend on .raw files. Though we don't actually make the .result file.
$(filter %.result,$(obj_files)): %.result: %.raw
echo "target: $@ prereq: $<"
%.c %.raw:
touch $@
clean:
rm -f $(src_files)
Pattern Rules
模式规则经常被使用,但很混乱。您可以通过两种方式来看待它们:
- 定义您自己的隐式规则的方法
- 静态模式规则的更简单形式
我们先从一个例子开始:
# 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 $@
Double-Colon Rules
双冒号规则很少使用,但允许为同一目标定义多个规则。如果这些是单冒号,则会打印警告,并且只会运行第二组命令。
all: blah
blah::
echo "hello"
blah::
echo "hello again"
Commands and execution
Command Echoing/Silencing
在命令前添加@以阻止其打印
您还可以使用 -s 运行 make 以在每行之前添加 @
all:
@echo "This make line will not be printed"
echo "But this will"
Command Execution
每个命令都在新的 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`
Default Shell
The default shell is /bin/sh
. You can change this by changing the variable SHELL:
SHELL=/bin/bash
cool:
echo "Hello from bash"
Double dollar sign
如果你想要一个字符串有$符号,你可以使用$$。这是在 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 make variable" in the shell
echo $(make_var)
Error handling with -k
, -i
, and -
运行 make 时添加 -k,即使出现错误也可以继续运行。如果您想立即查看 Make 的所有错误,这很有帮助。
在命令前添加 - 以抑制错误
添加 -i 以使每个命令都发生这种情况。
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 use of make
要递归调用 makefile,请使用特殊的 $(MAKE) 而不是 make,因为它会为您传递 make 标志,并且本身不会受到它们的影响。
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
Export, environments, and recursive 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 命令时,可以使用导出指令使其可供子 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
Arguments to make
There’s 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 and modification
变量有两种类型:
递归(use =) - 仅在使用命令时查找变量,而不是在定义命令时查找变量。
简单扩展(使用 :=) - 就像普通的命令式编程一样 - 只有那些到目前为止定义的才会被扩展
# 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)
Use +=
to append
foo := start
foo += more
all:
echo $(foo)
Command line arguments and override
您可以使用 override 覆盖来自命令行的变量。这里我们用 make option_one=hi 运行 make
# 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)
List of commands and define
Define 指令不是一个函数,尽管它看起来可能是这样。我发现它很少使用,所以我不会详细介绍,但它主要用于定义罐头食谱,并且与 eval 函数配合得很好。
Define/endef 只是创建一个设置为命令列表的变量。请注意,这与命令之间使用分号有点不同,因为每个命令都在单独的 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)
Target-specific variables
可以为特定目标设置变量
all: one = cool
all:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
Pattern-specific variables
您可以为特定目标模式设置变量
%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
Conditional part of Makefiles
Conditional if/else
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
Check if a variable is empty
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
Check if a variable is defined
ifdef 不扩展变量引用;它只是查看某些内容是否已定义
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "but bar is not"
endif
$(MAKEFLAGS)
此示例向您展示如何使用 findstring 和 MAKEFLAGS 测试 make 标志。使用 make -i 运行此示例以查看它打印出 echo 语句。
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
Functions
First Functions
函数主要用于文本处理。使用 $(fn,arguments) 或 ${fn,arguments} 调用函数。 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)
String Substitution
$(patsubst pattern,replacement,text) 执行以下操作:
“在文本中查找与模式匹配的空格分隔的单词,并将其替换为替换。这里模式可能包含一个‘%’,它充当通配符,匹配单词中任意数量的任何字符。如果替换也包含‘%’, ‘%’ 被与模式中 ‘%’ 匹配的文本替换。只有模式和替换中的第一个 ‘%’ 会以这种方式处理;任何后续 ‘%’ 都不会改变。” (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)
The foreach function
foreach 函数如下所示:$(foreach var,list,text)。它将一个单词列表(用空格分隔)转换为另一个单词列表。 var 设置为列表中的每个单词,并且每个单词的文本都会扩展。
这会在每个单词后面添加一个感叹号:
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)
The if function
if 检查第一个参数是否非空。如果是,则运行第二个参数,否则运行第三个参数。
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
The call function
Make 支持创建基本函数。您只需创建一个变量即可“定义”该函数,但使用参数 ( 0 ) 、 (0)、 (0)、(1) 等。然后您可以使用特殊的 call 内置函数来调用该函数。语法为$(调用变量,参数,参数)。 $(0) 是变量,而 ( 1 ) 、 (1)、 (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)
The shell function
shell - 这调用了 shell,但它用空格替换换行符!
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
Other Features
Include Makefiles
include 指令告诉 make 读取一个或多个其他 makefile。 makefile 中的一行如下所示:
include filenames...
当您使用像 -M 这样的编译器标志基于源代码创建 Makefile 时,这特别有用。例如,如果某些 c 文件包含标头,则该标头将添加到 gcc 编写的 Makefile 中。我在 Makefile Cookbook 中详细讨论了这一点.
The vpath Directive
使用 vpath 指定某些先决条件集所在的位置。格式为 vpath <目录,空格/冒号分隔> 可以有一个 %,它匹配任何零个或多个字符。您还可以使用变量 VPATH 全局执行此操作
vpath %.h ../headers ../other-directory
# Note: vpath allows blah.h to be found even though blah.h is never in the current directory
some_binary: ../headers blah.h
touch some_binary
../headers:
mkdir ../headers
# We call the target blah.h instead of ../headers/blah.h, because that's the prereq that some_binary is looking for
# Typically, blah.h would already exist and you wouldn't need this.
blah.h:
touch ../headers/blah.h
clean:
rm -rf ../headers
rm -f some_binary
Multiline
当命令太长时,反斜杠(“\”)字符使我们能够使用多行
some_file:
echo This line is too long, so \
it is broken up into multiple lines
.phony
将 .PHONY 添加到目标将防止 Make 将虚假目标与文件名混淆。在此示例中,如果创建了 clean 文件,make clean 仍将运行。从技术上讲,我应该在每个示例中使用 all 或 clean,但我没有保持示例干净。此外,“虚假”目标的名称通常很少是文件名,实际上许多人会跳过这一点。
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. The shell will incorrectly expand these otherwise, but we want to send the * directly to the find command.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
# Prepends BUILD_DIR and appends .o to every src file
# As an example, ./your_dir/hello.cpp turns into ./build/./your_dir/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)