11、自动生成依赖关系

值得思考的问题

目标文件(.o)是否只依赖与源文件(.c)?

编译器如何编译源文件和头文件?

test.out->test.c test.h

编译行为带来的缺陷:

预处理器将头文件中的代码直接插入源文件。编译器只通过预处理后的源文件产生目标文件。

因此,

规则中以源文件为依赖,命令可能无法执行。

因为可能.h修改,.c可能没有修改。

下面的makefile有没有问题?

OBJS :=func.o main.o  //两个源文件,两个目标文件

hello.out :$(OBJS)

        @gcc -o $@ $^

        @echo "Target file==>$@"

$(OBJS): %.o : %.c 

        @gcc -o $@ -c $^   

当只改变.h文件时,编译器不会认为.c是最新的,所以不会重新编译。在$(OBJS): %.o : %.c 后边加上func.h也不行,gcc编译时不考虑.h文件,所以将$^ 改为$<只编译第一个源文件,就通过了。

OBJS :=func.o main.o  
hello.out :$(OBJS)
@gcc -o $@ $^
@echo "Target file==>$@"
$(OBJS): %.o : %.c func.h
#mo shi gui ze ,wei mu biao sheng cheng zhen zheng guize 
@gcc -o $@ -c $<  

试验中解决方案的问题:

头文件作为依赖条件出现于每个目标对应的规则中。

当头文件改动,任何源文件都将被重新编译(编译低效)。

当项目中头文件数量巨大时,makefile将很难维护。

疯狂想法:

通过命令自动生成对头文件的依赖。

将生成的依赖自动包含进makefile中。

当头文件改动后,自动确认需要重新编译的文件。

预备工作(原材料)

linux 命令 sed.

编译器依赖生成选项 gcc-MM( gcc-M).

linux中的sed命令:

sed是一个流编辑器,用于流文本的修改(增、删、查、改)。

sed可用于流文本中的字符串替换。

sed的字符串替换方式为:sed  's:src:des:g'

echo  "test=>abc+abc=abc" | sed 's:abc:xyz:g' //重定向到sed

    test=>xyz+xyz=xyz

sed的正则表达式支持:

在sed中可以用正则表达式匹配替换目标。

并且可以使用匹配的目标生成替换结果。

sed 's,\(.*\)\.o[ :]* , objs/\1.o : ,g' (正则表达式匹配目标 ) (将匹配结果进行替换)[ :]表示无论有多少:都可以匹配成功,替换成一个

gcc关键编译选项

生成依赖关系:

获取目标的完整依赖关系:gcc-M test.c

获取目标的部分依赖关系:gcc-MM test.c 自己编写的 -E表示初步解析就可以,只要结果就可以没必要其它操作,命令:

gcc -MM -E main.c | sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g'

echo "/a/b/c/d/main.o ::::::main.c func.h" |sed 's,\(.*\)\.o[ :]*,x/y/z/\1.o: ,g'

小技巧:拆分目标的依赖:

将目标的完整依赖拆分为多个部分依赖。

.PHONY:a b c

test:a b c

    @echo "$^"

等价于:

.PHONY:a b c 

test: a b

test: b c

test:

        @echo "$^"

12、makefile中的include关键字

类似C语言中的include。

将其他文件的内容原封不动的搬入当前文件。

语法:include filename

例:include foo.make *.mk $(var)

具体文件名,*.mk当前目录中所有mk文件搬到当前makefile中来。$(var)包含这个变量的内容

make对include关键字的处理方式:

在当前目录搜索或指定目录搜索目标文件。

搜索成功:将文件内容搬入当前makefile中

搜索失败:产生警告

以文件名作为目标查找并执行对应规则。

当文件名对应的规则不存在时,最终产生错误。

@touch test.txt 创建文件夹test.txt

.PHONY:all
include test.txt
all:
@echo "this is all"
test.txt:
@echo "this is text.txt"

@touch test.txt

2makefile中命令的执行机制:

规则中的每个命令默认是在一个新的进程中执行(Shell)。

可以通过接续符(;)将多个命令组合成一个命令。

组合的命令依次在同一个进程中被执行。

set-e指定发生错误后立即退出执行。

.PHONY:all

all:

    mkdir test

    cd test

    mkdir subtest //进不去

.PHONY:all
all:
set -e;\
mkdir test;\
cd test;\

mkdir subtest

3解决方案的初步思路:

通过gcc -MM和sed得到.dep依赖文件(目标的部分依赖)

技术点:规则中命令的连续执行。

通过include指令包含所有的.dep依赖文件。

技术点:当.dep依赖文件不存在时,使用规则自动生成。

#makefile
.PHONY:all clean
MKDIR :=mkdir
RM :=rm -fr
CC := gcc
SRCS :=$(wildcard *.c) #获得后缀.c文件列表
DEPS :=$(SRCS:.c=.dep) #模式替换为.dep
-include $(DEPS) #包含依赖文件 目标的部分依赖 -号不包括警告
all:
@echo "all"
%.dep : %.c //找不到DEPS 就找规则,模式
@echo "creating $@..."  #生成依赖文件
@set -e;\ #连续执行命令 -E表示预处理
$(CC) -MM -E $^(得到依赖的其他文件) | sed 's,\(.*\)\.o[ :]*(正则表达式匹配.o文件),objs/\,1.o : (加前缀),g' >(将结果重定向到文件中,不用打印到屏幕) $@  (重定向到依赖文件)
clean :

$(RM) $(DEPS)

生成 main.dep,内容:objs/main.o : main.c func.h

生成 func.dep, 内容: objs/func.o : func.c func.h

13、自动生成依赖关系下

如何在makefile中组织.dep文件到指定目录?

思路:

当include发现.dep文件不存在:

1、通过规则和命令创建deps文件。

2、将所有.dep文件创建到deps文件夹。

3、.dep文件中记录目标文件的依赖关系。

初步设计:

$(DIR_DEPS):

        $(MKDIR)$@ #创建文件夹

$(DIR_DEPS)/(将.dep放到这个目录中)%.dep : $(DIR_DEPS)(通过依赖触发上边的语句创建文件夹)%.c

        @echo "Creating $@..."

        @set -e; \

        $(CC) -MM -E $(filter %.c,$^)(过滤只要.c) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' $@

在make all 的时候创建文件夹可以?

all : $(DIR_DEPS)

@echo "all"

执行:

creating main.dep...

creating func.dep...

makir deps

all

先创建.dep,后创建deps文件夹,include的机制,先看看文件存在不,不存在,找对应规则执行命令。与期望相反了

$(filter %.c,$^) 依赖过滤, 只要.c

为什么一些.dep依赖文件会被重复创建多次?

分析:

deps文件夹的时间属性会因为依赖文件创建而发生改变。

make发现deps文件夹比对应的目标更新。

触发相应规则的重新解析和命令的执行。

creating deps/main.dep...
creating deps/func.dep...
creating deps/main.dep...

make all时,先包含,不存在,执行.dep规则,首先创建main.dep依赖文件,因为规则被实际解析出来了,make将依赖关系记录下来,然后创建func.dep依赖文件,在创建func.dep时,导致deps文件夹时间属性的更新,这个更新使得make发现deps文件夹比main.dep这个文件更新了,然后make触发@echo "creating $@..."@set -e;\$(CC) -MM -E $(filter %.c,$^)| sed 's,\(.*\)\.o[ :]*,objs/\,1.o : ,g' > $@  规则的执行。当.c文件多时,会不停的创建.dep,造成死循环。

解决:使用if语句

  RM :=rm -fr
CC := gcc
DIR_DEPS :=deps
SRCS :=$(wildcard *.c)
DEPS :=$(SRCS:.c=.dep)
DEPS :=$(addprefix $(DIR_DEPS)/,$(DEPS))
#mei you zhe ge xia bian $(DIR_DEPS)/%.dep pi pei bu dao
all:
@echo "all"
ifeq ("$(MAKECMDGOALS)","all") #make houbian de min ling shi all
-include $(DEPS) #xian bao han
endif
ifeq ("$(MAKECMDGOALS)","") #make houbian de min ling shi kong
-include $(DEPS) #xian bao han
endif
$(DIR_DEPS) :
$(MKDIR) $@
ifeq ("$(wildcard $(DIR_DEPS))", "") #shi fou cun zai DIR_DEPS
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
@echo "creating $@..."
@set -e;\
$(CC) -MM -E $(filter %.c,$^)| sed 's,\(.*\)\.o[ :]*,objs/\,1.o : ,g' > $@  
#>cong ding xiang dao wenjian 
clean :

$(RM) $(DIR_DEPS)

include暗黑操作一:

使用减号(-)不但关闭了include发出的警告,同时关闭了错误,当错误发生时make将忽略这些错误。

include暗黑操作二:

如果include触发规则创建了文件,之后还会发生什么?

.PHONY : all
-include test.txt
all :
@echo "this is all"
test.txt :b.txt
@echo "createint $@..."

@echo "other:;@echo "this is other" " > test.txt #> 把内容写入到test.txt中

test.txt不存在,则通过规则创建出来,里边的内容被重新包含到include中,第一条规则就是include,打印@echo "this is other" ,创建内容之后会把内容重新载入包含的地方。搬到makefile中。

include暗黑操作三:

如果include包含的文件存在,之后会发生什么?

touch b.txt  //b.txt就是时间戳上最新的

cat test.txt //查看test.txt的内容

.PHONY : all
-include test.txt
all:
@echo "this is all"
test.txt : b.txt

@echo "creating $@..."

当include一个文件后,make检查文件名对应的规则有没有,如果有,会进一步检查这个规则的依赖时间戳与目标的时间戳哪个更新,如果依赖的时间戳更新,那么就会执行这个规则下的命令了。

4

.PHONY : all
-include test.txt
# all : a.txt  内容搬过来
all:
@echo "$@ : $^"
test.txt : b.txt
@echo "creating $@..."

@echo "all : c.txt" > test.txt

make all 后打印all : a.txt

touch b.txt后最新是b.txt

make all 后打印all : c.txt

b.txt最新执行test.txt下的命令,test.txt改变为all : c.txt, 这样会把更新后的内容包含进来,于是打印all : c.txt

make在执行相应规则的过程中,如果需要包含的文件改变了,就会重新加载改变的内容。

关于include的总结一:

当目标文件不存在:以文件名查找规则,并执行。

当目标文件不存在,且查找到的规则中创建了目标文件:

将创建成功的目标文件内容包含进当前makefile。

关于include的总结二:

当目标文件存在:

将目标文件包含进当前makefile。

以目标文件名查找是否有相应规则。

yes:比较规则的依赖关系,决定是否执行规则的命令。

no:null(无操作)

关于目标文件总结三:

当目标文件存在,且目标名对应的规则被执行。

规则中的命令更新了目标文件:make重新包含目标文件,替换之前包含的内容。

目标文件未被更新:null(无操作)

14、自动生成依赖关系

疯狂想法具体实现:

include   %.dep-->(文件夹deps 源文件%.c)

all:=>$(EXT)-->文件夹exes objs %.o

#makefile
.PHONY:all clean rebuild
MKDIR :=mkdir
RM :=rm -fr
CC := gcc
DIR_DEPS :=deps
DIR_EXES :=exes
DIR_OBJS :=objs
DIRS :=$(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)
EXE :=app.out
EXE :=$(addprefix $(DIR_EXES)/,$(EXE))
SRCS :=$(wildcard *.c)
OBJS :=$(SRCS:.c=.o)
OBJS :=$(addprefix $(DIR_OBJS)/,$(OBJS))
DEPS :=$(SRCS:.c=.dep)
DEPS :=$(addprefix $(DIR_DEPS)/,$(DEPS))
#mei you zhe ge xia bian $(DIR_DEPS)/%.dep pi pei bu dao
all: $(DIR_OBJS) $(DIR_EXES) $(EXE)
ifeq ("$(MAKECMDGOALS)","all") #make houbian de min ling shi all
-include $(DEPS) #xian bao han
endif
ifeq ("$(MAKECMDGOALS)","") #make houbian de min ling shi kong
-include $(DEPS) #xian bao han
endif
$(EXE) : $(OBJS) 
$(CC) -o $@ $^   #LIAN JIE
@echo "success! target=>$(EXE)"
$(DIR_OBJS)/%.o : %.c
$(CC) -o $@ -c $(filter %.c, $^)
#MUBIAOWENJIAN tongguo moshi guize chansheng zhenzheng bianyi xinagguan guize
$(DIRS) :
$(MKDIR) $@
ifeq ("$(wildcard $(DIR_DEPS))", "") #shi fou cun zai DIR_DEPS
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
@echo "creating $@..."
@set -e; \
$(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@  
#>cong ding xiang dao wenjian 
clean :
$(RM) $(DIRS)
rebuild :
@$(MAKE) clean
@$(MAKE) all

make[1]: 正在进入目录 `/home/delphi/make'
rm -fr deps exes objs
make[1]:正在离开目录 `/home/delphi/make'
make[1]: 正在进入目录 `/home/delphi/make'
mkdir deps
creating deps/main.dep...
creating deps/func.dep...
make[1]:正在离开目录 `/home/delphi/make'
make[1]: 正在进入目录 `/home/delphi/make'
mkdir objs
mkdir exes
gcc -o objs/func.o -c func.c //编译源码
gcc -o objs/main.o -c main.c
gcc -o exes/app.out objs/func.o objs/main.o   #LIAN JIE
success! target=>exes/app.out

make[1]:正在离开目录 `/home/delphi/make'

当修改.h文件中的打印内容时,不会重新打印,而说没说做的。

注意事项:当.dep文件生成后,如果动态的改变头文件间的依赖关系,那么make无法检测到这个改变,进而做出错误的编译决策。

解决方案:

将依赖文件名作为目标加入自动生成的依赖关系中。

通过include加载依赖文件时判断是否执行规则。

在规则执行时重新生成依赖关系文件。

最后加载新的依赖文件。

$(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' > $@ 

新增加一个$@.

dep作为目标依赖func.c func.h define.h。

objs/func.o deps/func.dep: func.c func.h define.h

objs/func.o : func.c func.h define.h new.h other.h

每次include的时候就会利用暗黑操作检查deps/func.dep的依赖文件是不是在时间戳上更新了,如果有重新解释执行.dep文件,把更新的.dep包含进makefile,.c就被重新编译了,把刚才的改动带到最终的可执行程序中。

小结:

makefile中可以将目标的依赖拆分写到不同的地方。

include关键字能够触发相应规则的执行。

如果规则的执行导致依赖更新,可能导致再次解释执行相应规则。可能重复创建,严重死循环

依赖文件也需要依赖于源文件得到正确的编译决策。只要.c .h改变就重新make

自动生成文件间的依赖关系能够提高makefile的移植性。

猜你喜欢

转载自blog.csdn.net/ws857707645/article/details/80778027