Makefile Usage
语法
targets: prerequisites
command
command
command
prerequisites 是先决条件, 类似依赖
定义 target
hello:
echo "Hello, World"
运行 make 后执行 echo
可以在 :
后写这个 target 的 prerequisites
hello: hello.txt
cat hello.txt
如果没有这个文件就会报错
这样就可以构建一个依赖链条, make 会按照依赖顺序来处理
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
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
在命令前加 @
可以让 make 不打印这一行命令, 或者用 make -s
为每行都加 @
在命令前加 -
可以忽略并打印这个命令的错误
多个 target
all: f1.o f2.o
f1.o f2.o:
echo $@
# Equivalent to:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o
$@
是一个自动变量
变量
定义
行首的空格会被删除, 行末的空则不会
未定义的变量会是一个空字符串
=
会延迟/递归展开, 在使用时才会计算
=:
立即/简单展开, 在定义时就会计算
# 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)
?=
在没有设置时设置变量
命令列表
使用 define
/ endef
来创建一个命令列表
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 声明变量
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)
使用
使用变量用 $()
和 ${}
使用变量
如果要使用 shell 变量则需要 $$
all:
echo $$HOME
自动变量
$@
- target 的名称$?
- 所有比 target 新的 prerequisites$^
- 所有 prerequisites$<
- 第一个 prerequisite
hey: one two
# Outputs "hey", since this is the target name
echo $@
# Outputs all prerequisites newer than the target
echo $?
# Outputs all prerequisites
echo $^
# Outputs the first prerequisite
echo $<
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two
通配符
有 *
和 %
, *
用于 shell 字符匹配, %
用于模式匹配
*
不要直接用于变量声明, 因为当其没有匹配到任何文件时不会展开
推荐的做法是使用 wildcard
函数: $(wildcard *.o)
, 包装了之后如果没有匹配到则就是空字符串
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)
模式规则
隐式规则
隐式规则是一种自动规则
CC
: Program for compiling C programs; defaultcc
CXX
: Program for compiling C++ programs; defaultg++
CFLAGS
: Extra flags to give to the C compilerCXXFLAGS
: Extra flags to give to the C++ compilerCPPFLAGS
: Extra flags to give to the C preprocessorLDFLAGS
: Extra flags to give to compilers when they are supposed to invoke the linker
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*
上面这一段, 没有找到 blah.o
就会自动调用 gcc -g
来编译一个 blah.o
, 而编译这个需要 blah.c
, 就会先运行 blah.c
模式规则
使用 %
来匹配一种模式
语法:
target-pattern:
commands
比如下面的 Makefile
objects = foo.o bar.o
%.c:
touch $@
这里的 %.c
相当于会变成:
objects = foo.o bar.o
foo.c:
touch $@
bar.c:
touch $@
静态模式规则
语法:
targets...: target-pattern: prereq-patterns ...
commands
对于所有 target-pattern
匹配到的 targets, 应用 prereq-pattern
objects = foo.o bar.o all.o
all: $(objects)
$(CC) $^ -o all
$(objects): %.o: %.c
$(CC) -c $^ -o $@
会变成:
objects = foo.o bar.o
foo.o: foo.o
$(CC) -c $^ -o $@
bar.o: bar.o
$(CC) -c $^ -o $@
条件控制
使用 if/else
来控制条件
ifeq
- 变量是否相同ifneq
- 变量是否不同ifdef
- 变量是否已被定义
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
函数
函数调用的形式: $(fn, arguments)
subst
$(subst search, replace)
用于字符串替换
将 not
替换成 totally
bar := ${subst not,"totally", "I am not superman"}
all:
@echo $(bar)
patsubst
$(patsubst pattern,replacement,text)
模式匹配字符串替换
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 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 this-is-not-empty,then,else)
检测第一个参数是否不为空, 是执行第二, 否执行第三个参数
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
call
$(call variable,param,param)
调用定义的函数
首先要定义一个函数, 定义的方式是:
$(0)
- 函数名
$(1)
- 第一个参数
$(2)
- 第二个参数
....
下面这个定义的这个函数就是纯输出的作用
fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
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)
定义函数相当于定义一个变量
define func
echo "Function name: $(0), First: $(1)"
endef
all:
${call func, hello}
shell
all:
@echo $(shell ls -la)
要注意, 换行会被替换成空格
filter
$(filter pattern, text)
, text
是一组空格分割的字符串
obj_files = foo.result bar.o lose.o
filtered_files = $(filter %.o,$(obj_files))
all:
@echo $(filtered_files)
MISC
指定 shell
SHELL=/bin/bash
cool:
echo "Hello from bash"
递归 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
使用 $(MAKE)
而不是直接使用 make
, 前者会自动传递标志并且不会受到自身的影响
导出变量
使用 .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
包含文件
和 C 一样
include filenames
这在 gcc -MMD
生成依赖文件时有用
包含与每个 .c
源文件对应的 .d
依赖文件
-include $(sources:.c=.d)
这里加 -
, .d
不存在也不会报错
指定搜索路径
vpath <pattern> <directories, space/colon separated>
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
.phony
可以用 .PHONY
来声明某个名字不是一个实际文件
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
这里如果不加 .PHONY clean
, 在运行 make clean
时就会认为 clean
是一个目标文件, 而我们已经通过 touch
创建, 由于 clean target
无 prerequisites 无需更新, 所以不会运行 clean target
中的内容
.delete_on_error
如果 target中的一个命令返回非 0 的退出状态, 就会删除 target 生成的文件
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false