make执行的两个阶段
- 读取所有makefile文件(include指定的,命令行选项-f指定的,其余不用考虑,不常用的),内建所有变量,明确规则和隐含规则,建立目标和依赖之间的关系链表。
- 根据阶段1建立的关系依赖链表,决定哪些目标需要更新。
在make执行第一阶段中如果变量和函数被展开,那么称此展开为立即展开,在第二阶段的展开称为延后展开。区别如下:
立即展开就是变量定义时,就确定变量的值,比如a=1,b=a+1,如果是立即展开,那么b就确定为2了,而延后展开不同,同样a=1,b=a+1,需要等到使用变量b的时候才展开,但是如果在b=a+1之后,且在使用变量b之前,a的值变为2了,那么使用b的时候就变为b=2+1=3了。那么哪些情形是立即展开,哪些是延后展开呢?定义变量的三种方法,如果变量a通过a+=的方式赋值,且之前a是一个简单变量,即通过a:=的方式定义,那么a是立即展开的,其余情况都是延后展开。
a=val1
b:=val2 #仅这种方式定义的变量称为简单变量
c+=$(b)
.PHONY:all
x=foo #x不是简单变量
y=$(x) b #这种方式定义y,是延后展开
z := $(x) b #z是简单变量,立即展开为foo b
w += $(y) b #w不是简单变量,因为之前没有定义,延后展开
u := sample #u是简单变量
u += $(x) b #u在此之前是简单变量,使用+=赋值时立即展开,u=sample foo b
x = later
all:
@echo "x is $(x)"
@echo "y is $(y)"
@echo "z is $(z)"
@echo "w is $(w)"
@echo "u is $(u)"
#output:
#x is later
#y is later b
#z is foo b
#w is later b b
#u is sample foo b
makefile的规则
- 一个简单的栗子
本部分比较复杂,前后勾连,我们先看一个例子
foo.o:defs.h foo.c
cc -c foo.c
foo.o是这个规则的目标,def.h foo.c是目标的依赖,第二行是规则的命令。上述规则告诉我们如何重建目标文件,以及何时重建。
- 通配符
makefile中可以使用的通配符有三个(*, ? [...]),其意义和shell中的一样。统配符可以直接用在如下两种情形下:
- 规则的目标或依赖
- 规则的命令中,此时通配符的处理是在shell执行的时候
其余情况不能直接使用,需要配合wildcard函数来使用。
- 通配符使用
objs = *.o
foo : $(objs)
cc -o foo $(objs)
本意是foo依赖目录下所有的.o文件,如果所有的.o文件都存在,那么 能正确编译出foo,但是如果删掉所有.o后,就会报错,说没有重建*.o的规则,这里是因为使用通配符的时候出了错。应该改为
objs = $(wildcard *.o)
foo : $(objs)
cc -o foo $(objs)
自动化变量
仅列出常用的,其余的暂时不需要。
$@:规则目标名
$<:规则第一个依赖的文件名
$^:规则所有依赖的文件列表
$(@D):目标文件目录部分,不包括最后的斜杠,如目录不存在斜杠,则其值为".",表示当前目录
$(@F):目标文件的文件名部分
$(<D):依赖的第一个文件的目录部分
$(<F):依赖的第一个文件的文件名部分
库文件的搜索目录
当重建一个目标时,所依赖的文件名为-INAME,make将执行以下搜索:
- 在当前目录搜索名为libNAME.so文件
- 搜索VPATH或vpath指定的目录
- 系统库文件默认路径,"/lib,/usr/lib,/usr/local/lib"
- 上述路径下名为libNAME.a的文件
foo : foo.c -lcurses
cc $^ -o $@
一般情况下,我们的库文件是不会在make的时候重建的,所以只要能在上述4个步骤中的某一个找到它就行。下面是一个具体的栗子。
![](https://img.haomeiwen.com/i3076894/934064f52a4bf96f.png)
在目录dynamic_lib中有一个简单的动态库,在static_lib中有一个简单的静态库,静态库和动态库的生成见你的动态库还在报错?。文件main.c引用了两个库中的函数,我们的Makefile应该怎么写呢?
INCLUDE = -I./dynamic_lib -I./static_lib
all:main
main:main.c
gcc $(INCLUDE) main.c -L./dynamic_lib -L./static_lib \
-ldynamic_help -lstatic_lib -o main
.PHONY:clean
clean:
rm main
有一下几点需要格外注意:
- 如要使用动态库,在编译的时候需要指定库的位置,用-L./dynamic_lib指定,如果要使用的库已经在默认搜索目录(/lib,/usr/lib,/usr/local/lib)则不需要再指定目录,只需要用-lXXX来引用库libXXX.so。静态库也一样,只不过静态库名一般为libXXX.a。
![](https://img.haomeiwen.com/i3076894/e29cb87e48e74a7b.png)
- 如要使用动态库或静态库,在编译的时候,需要指定相应头文件的位置,用-I./dynamic_lib来指定,即上面的INCLUDE变量。
![](https://img.haomeiwen.com/i3076894/97e0eb83380a4416.png)
- 在运行使用了动态库的程序时,程序需要知道动态库的位置在哪,如果动态库不在默认路径上,则需要用LD_LIBRARY_PATH环境变量来指定。而由于静态库在编译的时候就相当于把整个库联编到可执行程序(本例中为main这个可执行程序),在运行的时候就不需要再指定静态库的位置了。
![](https://img.haomeiwen.com/i3076894/a4c14c841f285ca3.png)
我们写一个运行脚本,把环境变量LD_LIBRARY_PATH设置好,进行验证。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./dynamic_lib/
./main
执行结果如下:
![](https://img.haomeiwen.com/i3076894/bb338256b20f3373.png)
伪目标
使用场景有两个,这里只介绍一个,那就是如果我们的规则是执行一段命令,而不是重建某个文件,如clean。
.PHONY : clean
clean:
rm *.o
#执行make clean就能清楚所有的.o文件
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj:
rm *.o
cleandiff:
rm *.diff
强制目标:FORCE
FORCE仅仅是一个符号,也可以用其他任意未用过的符号替代,FORCE出现在依赖列表中,如果FORCE是一个真正存在的文件名,那么将根据文件的新旧来决定是否重建target,但是FORCE是一个不存在的文件,且FORCE本身的依赖是空,规则命令也是空,在这种情下,依赖与FORCE的目标将无条件被重建,看例子。
#FORCE充当目标all的依赖
all:FORCE
@echo "must execute here"
#相当于FORCE是一个目标,其依赖和规则命令都为空
FORCE:
下面是一个具体的栗子:下面这个例子中,终极目标gen_file,这个目标依赖文件test.log,即如果文件test.log不存在或比gen_file文件新,将会执行echo "gen_file",所以在第一次执行make时,由于test.log文件不存在,会执行一次,并生成文件test.log,此后再执行将不会有任何输出,因为文件test.log并没有变得更新,所以make觉得不需要执行。
file_name=test.log
all:gen_file
gen_file:$(file_name)
@echo "gen_file"
$(file_name):
@echo "echo file_name $(file_name)"
@rm -rf $(file_name) && echo `date` > $(file_name)
要想每次都执行日期更新,那么需要文件test.log依赖于一个永远是最新的东西,那么就是FORCE,如下所示,由于FORCE并不存在,所以每次都需要更新test.log也就会执行echo和rm命令了。
file_name=test.log
all:gen_file
gen_file:$(file_name)
@echo "gen_file"
$(file_name):FORCE
@echo "echo file_name $(file_name)"
@rm -rf $(file_name) && echo `date` > $(file_name)
FORCE:
.PHONY:FORCE
包含其他makefile文件
在makefile1中使用include包含另一个makefile2相当于把makefile2中的内容贴到include处合并为一个makefile文件。然后该怎么执行就怎么执行。
#makefile1
include makefile2
all:
@echo "target all"
#makefile2
__all:
@echo "target __all"
如果执行make -f makefile1,那么先包含文件makefile2,那么__all成为第一目标,也就是终极目标,所以默认重建__all,所以输出target __all。
静态模式
静态模式定义了一种比较简约的目标依赖的书写方法,有三种不同的写法,意义等价。完全不用纠结其中的意义。
objs = a.o b.o c.o d.o
$(objs):%.o:%.c
cc -c $< -o $@
#或者是如下写法
%.o : $.c
cc -c $< -o $@
#或者
.c.o:
cc -c $< -o $@
函数
- subset
$(subset ee,EE,feet on the street)
#得到fEET on the strEEt
- patsubset
s=a b acb adb
$(patsubst a%b,sss,$(s))
#输出:a b sss sss,其中a b不会替换为sss
- strip
s = a b c d
$(strip $(s))
#返回无前导、无后缀空白,单词间用一个空格分隔的字符串
- join
$(join a b c, .c .o)
#a.c b.o c
- wildcard
#列出当前目录下符合模式的文件名
$(wildcard *.c)
- foreach
dirs = a b
files = $(foreach dir, $(dirs), $(wildcard $(dir)/*))
#输出目录a b下的所有文件名,用空格分隔
- if
SUBDIR=$(if $(src_dir) $(src_dir),/home/)
#如果src_dir不空,那么SUBDIR就是src_dir否则是/home/
- shell函数:执行时用``包括起来就行。
网友评论