Learning by Doing.
Makefile学习
Makefile规则
1
2
3
4
tareget ... : prerequisites ...
command
...
...
target是目标文件,可以是Object File 也可以是执行文件,还可以是标签(label), prerequisites是生成target需要的文件或目标
command是make需要执行的指令
如果prerequisites中有任何一个文件比target要新,command中的命令就会被执行
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
上面的代码中,目标文件中包含了执行文件和中间目标文件(即*.o),依赖文件就是目标文件冒号后面的文件。这样就构成了最终的目标文件与原始代码之间的依赖关系。
在依赖关系后,后续的command定义了如何生成目标文件的命令,以tab开头。
clean是一个动作名字,执行其后的命令可以需要在make后手动添加标签,即 make clean
,类似地,可以在一个makefile中定义不用的编译或编译无关的命令,如打包/备份等。
变量
在makefile中可以使用变量来简化文件的编写。在makefile中变量可以被视为字符串,类似于C中的宏?
例如:
1
object = main.o kbd.o command.o display.o insert.o searach.o files.o utils.o
则可以通过$(objects)来使用这个变量。这样,上面的代码就可以化简为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
自动推导
make看到.o文件,会自动把.c文件作为他的依赖,并且 cc -c xxx.c也会被自动推导出来。则可进一步化简为:
1
2
3
4
5
6
7
8
9
10
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
.PHONY说明clean是一个伪目标文件。
.delete_on_error: 如果一条命令返回一个非零返回值,make将会停止执行命令。如果在makefile中添加了delete_on_error,那么在make失败时将会删除掉新编译出的target。
引用其他的Makefile
Makefile中可以使用include关键字将别的Makefile包含进来。include和文件名之间可以用一个或多个空格隔开。
Makefile寻找文件的顺序:
- 显式指出的文件位置
- 当前目录
- 如果make 执行时有-l 或 –include-dir参数,那么make就会在这个参数所指定的目录下去寻找。
- 如果<prefix>/include (e.g. /usr/local/bin或/usr/include)存在,make会到该目录下去寻找.
如果有文件没有找到,make会生成警告,但不会立刻退出,而是继续载入其他文件,在完成整个makefile的读取后,make再重试这些没有找到或无法读取的文件。若需要make跳过无法读取的文件,则需要在include前加一个’-‘.
规则
规则包含两个部分,一个是依赖关系,另一个是生成目标的方法。
1
2
3
4
tareget ... : prerequisites ...
command
...
...
Makefile中存在特殊变量VPATH,如果没有指明这个变量,make会在当前的目录中寻找文件的依赖关系。如果在当前目录中没有找到,会到VPATH所指明的目录中寻找。
1
VPATH = src:../headers
如上,makefile将会按照src, ../headers这样的顺序寻找,不同的目录之间用’:’分割。
另一个设置文件搜索路径的方法:使用make的vpath关键字
- vpath <pattern> <directories> : 为符合模式pattern的文件指定搜索目录
- vpath <pattern> : 清除符合模式pattern的文件的搜索目录
- vpath:清除所有被设置好的文件搜索目录
pattern中需要包含%,匹配零或若干字符。例如:
1
vpath %.h ../headers
表示在../headers目录下搜索所有以.h结尾的文件
隐式规则
- 编译C代码:n.o通过命令
$(CC) -c $(CPPFLAGS) $(CFLAGS)
,依赖于文件n.c,自动编译出.o文件 - 编译C++代码:n.o通过命令
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
,依赖于文件n.cc或n.cpp,自动编译出.o文件。 - 链接单个.o文件:n通过
$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
,自动编译成可执行文件。
其中使用的变量分别为:
- CC: 编译C代码使用的编译器,默认为cc
- CXX: 编译C++代码使用的编译器,默认为g++
- CFLAGS: C编译器使用的额外参数
- CXXFLAGS: C++编译器使用的额外参数
- CPPFLAGS: 传递给C预处理器的额外参数
- LDFLAGS: 调用链接器时链接器使用的参数
例如:
1
2
3
4
5
6
7
CC = gcc
blah: blah.o
blah.c:
clean:
rm -f blah*
输出为:
1
2
3
> make
gcc -c -o blah.o blah.c
gcc blah.o -o blah
Static Pattern Rules
1
2
targets...: target-pattern: prereq-patterns ...
commands
The essence is that 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.
主要用于自动将.c文件编译成.o文件,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// manual
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
18
//auto:
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
区别在于,使用通配符的方式引入了$(objects): %.o: %.c
自动为每个.o文件指定了其使用的其依赖的.c文件,而在手动方式中需要依次为每个.o文件指定.c文件,但是似乎只适用于.c文件与.o文件一对一的情况。
filter函数可以被用在static pattern rules中从变量中分离出正确的文件,例如:
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 # 取出Object中的.o文件
echo "target: $@ prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw # 取出object中的.result文件
echo "target: $@ prereq: $<"
%.c %.raw:
touch $@
clean:
rm -f $(src_files)
Pattern rules
%通配符匹配任何非空字符串,其他的字符匹配其自身。
1
2
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
完整的makefile版本:
1
2
3
4
5
6
7
8
9
10
11
CC = gcc
obj_files = blah.o test.o
.PHONY: all
all: $(obj_files)
$(obj_files): %.o: %.c
%.c:
等价于
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
双冒号规则:
使用双冒号允许为一个target定义多条指令,如果使用单冒号,将会报warnning 并且只执行第二条指令
伪目标
如果需要一次性生成多个可执行文件,但只要输入一个make,并且所有的目标文件都写在一个makefile中,可以通过伪目标来实现:
1
2
3
4
5
6
7
8
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
如上,声明了一个all伪目标,其依赖于其他三个目标。由于伪目标总是被执行,所以其依赖的三个目标总是不如all新,因此另外三条规则总是会被执行,于是就达到了一口气生成多个目标的目的。
书写命令
默认的shell为/bin/sh
,可以在makefile开头通过设置变量SHELL来改变
一、 显示命令
make默认将执行的命令行在命令执行前输出到屏幕,若使用@在命令行前,则这个命令将不被显示。
若在make执行时带入参数-n 或 –just-print,则只是显示命令而不执行,可用于调试makefile,查看命令的执行顺序。
make -s 或 –slient可以全面禁止命令的显示
二、命令执行
如果让下一条命令在上一条命令执行的基础上进行,则应该使用’;’分隔这两条命令,而不是换行。例如:
1
2
exec:
cd /home/hchen; pwd
中断或中止make,如果在执行make时在键盘输入ctrl+c
,make将会删除其刚刚生成的targets
三、命令出错
忽视命令出错,即无论正确不正确都认为是正确的,则在命令前加’-‘。例如:
1
2
clean:
-rm -f *.o
或给Make添加 -i或 –ignore-errors,忽略所有命令的报错
make -k 或 –keep-going,表示如果某条规则的命令出错了,那么终结该命令的执行,但继续执行其他规则
四、嵌套执行make
把不同模块或不同功能的源文件放在不同的目录中,可以在每个目录中都书写一个该目录的Makefile。例如,有一个子目录subdir,这个目录下有Makefile指明了这个目录下文件的编译规则,则父目录的Makefile可以这样, 其中应该使用$(MAKE)而不是使用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中使用export
export的语法与sh相同,功能相似,但是并不相关
export的功能是在上级makefile中创建的变量可以供被调用的下级makefile使用。
define
可以将相同的一组命令序列打包为一个特定的命令。
1
2
3
4
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
之后可以通过
1
2
foo.c:foo.y
$(run-yacc)
执行定义的命令. define的命令在一个分离的shell中执行。
命令行参数和override
使用override标识符来通过命令行覆盖定义的变量,例如 在命令行中make option_one=hi
:
1
2
3
4
5
6
override option_one = did_override
option_two = not_override
all:
echo $(option_one)
echo $(option_two)
Target-specific variables
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
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
自动变量以及通配符
自动变量 | 含义 |
---|---|
$@ | target name |
$< | dependency 第一个文件的名字 |
$^ | 全部dependencies 名字,无重复 |
$? | prerequisites中比target更新的文件名 |
$+ | 全部dependencies名字,有重复,适用于在链接器中重复值有意义的情况 |
变量嵌套:可以把变量的真实值放到后面定义。例如:
1
2
include_dirs := -Ifoo -Ibar
CFLAGS := $(include_dirs) -O
这样前面的变量不能使用后面的变量,而只能使用他前面定义好了的变量
变量替换:可以替换变量中共有的部分,例如
1
2
foo := a.o b.o c.o
bar := $(foo:.o=.c)
将foo中的.o全部替换为.c
静态模式替换:依赖于被替换字符串中有相同的模式,模式中必须包含一个%字符。例如:
1
2
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
追加变量值:使用+=,例如:
1
2
objects = main.o foo.o bar.o utils.o
objects += another.o
变量定义
makefile中存在两种风格的变量定义:
- recursive: (
=
) 在命令被使用时产生而不是在被定义时生成完整的变量 - simply expanded: (
:=
) 在命令被定义时生成完整的变量
(可能看起来比较抽象,看看例子就懂了)
1
2
3
4
5
6
7
8
9
10
# Recursive variable. print "later"
one = one ${later_variable}
# Simply expanded variable. Will not print "later"
two := two ${later_variable}
later_variable = later
all:
echo $(one)
echo $(two)
因为在one被定义时,仍然保持为one ${later_variable}
,直到echo使用$(one)
时才会查找${later_variable}
对应的值。而two使用simply expanded定义,在定义时,到上面去查找latere_variable对应的值,而此时later_variable并没有被定义,为空值,因此two = two
因为此种特性,simply expanded可以用来向一个变量追加值,而recursive definitions将会抛出一个infinite loop error.
除了simplye expanded之外,还可以使用+=向变量追加值,两者的区别在于,+=默认使用一个空格分隔,而simply expanded可以自行调整空格的数量,见下例:
1
2
3
4
5
6
7
one = hello
one := ${one} three //输出为hello three
one := ${one}three //输出为hellothree
foo := start
foo +=moree //输出为start moree
?=
仅仅在变量没有被定义时才会产生定义,e.g.:
1
2
3
4
5
6
7
one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)
每一行末尾的空格不会被删除,而开头的空格会被删除,如果需要使用空格的变量,可以通过$(nullstring)来完成。
这里有个疑问:行末尾是指什么?在下面的例子中,with_spaces=hello ,最后输出的hello自带几个空格,而单独输出nullstring为”“,输出spacew为” “, 可以认为所谓的行开头指的是等号后面开始一直到本行结束,因此变量等于单个空格会直接被忽略而作为空值,若需要使用空格则需要再加一层引用。
1
2
3
4
5
6
7
8
9
10
11
with_spaces = hello # with_spaces has many spaces after "hello"
after = $(with_spaces)
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
echo "$(snullstring)"
echo "$(space)"
echo "$(after)"
echo start"$(space)"end
Makefile中的条件判断
if/else
1
2
3
4
5
6
7
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
判断是否为空
1
2
3
4
5
6
7
8
9
10
nullstring =
foo = $(nullstring)
all:
ifeq ($(strip $(foo)), )
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring), )
echo "nullstring doesn't even have spaces"
endif
判断变量是否已被定义
1
2
3
4
5
6
7
8
9
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "buf bar is not"
endif
$(makeflags)
$(makeflags)表示make时通过命令行传递的参数
Functions
$(fn, arguments)
, arguments之间不应当添加额外的空格, 除非需要。
String Substitution
$(patsubst pattern,replacement,text)
:从text中选择与pattern匹配的单词,将其替换为replacement。其中pattern可以包含‘%’,如果replacement中也包含%,那么%将会被其对应的字符串替换。仅仅pattern中的第一个%会以这种方式被替换,后续的%将会保留。
foreach
$(forrach var,list,text)
:其中var是list中的每个单词,对于list中的每个var,将其替换为text
if
$(if this-is-not-empty,then!,else!)
,判断第一个参数是否为空值,若非空,则执行then,否则执行else.
call
makefile通过创建变量来定义函数,并且通过$(0)
等向函数传递参数。
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 function
1
2
all:
echo $(shell ls -la)