自动生成依赖关系

值得思考的问题

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

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

自动生成依赖关系

编译行为带来的缺陷

预处理器将头文件中的代码直接插入源文件

编译器只通过预处理后的源文件产生目标文件

因此,

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

下面的 makefile 有没有问题?

自动生成依赖关系

问题的提出 

makefile

TARGET := hello.out
CC := gcc
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)

$(TARGET) : $(OBJS)
	$(CC) -o $@ $^

$(OBJS) : %.o : %.c 
	$(CC) -o $@ -c $<

当前的目录文件

自动生成依赖关系

 当我们修改了 foo.h 中的内容后,结果如下

自动生成依赖关系

并没有再次进行编译,这导致最终可执行文件没有重新生成。 

实验中的解决方案

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

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

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

疯狂的想法

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

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

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

预备工作 (原材料)

Linux 命令 sed

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

Linux 中的 sed 命令

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

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

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

自动生成依赖关系

sed 的正则表达式支持 

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

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

自动生成依赖关系

gcc 关键编译选项 

生成依赖关系

  • 获取目标的完整依赖关系

gcc -M test.c

  • 获取目标的部分依赖关系

gcc -MM test.c

小技巧:拆分目标的依赖

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

自动生成依赖关系

预备工作 

makefile

.PHONY : test a b c

test : a b
test : b c

test : 
	@echo "$^"

运行结果如下所示:

自动生成依赖关系

我们将 test 目标的依赖拆分成两部分,和直接将 test 目标的依赖写成一起是一样的。 

makefile 中的 include 关键字

类似 C 语言中的 include

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

自动生成依赖关系

make 对 include 关键字的处理 

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

  • 搜索成功:将文件搬入当前 makefile 中
  • 搜索失败:产生警告
    • 以文件名作为目标查找并执行对应规则
    • 当文件名对应的规则不存在时,最终产生了错误

初探 include 关键字 

makefile


.PHONY : all

include test.txt

all : 
	@echo "this is all"

test.txt :
	@echo "this is test.txt"
	@touch test.txt

test.txt

other :
	@echo "this is $@"

执行结果如下所示:

自动生成依赖关系

首先 make 会查找当前目录中是否存在 test.txt 文件,当前目录中存在该文件,所以 make 会将 test.txt 中的内容插入到 include 的地方,other 成为了顶层目标,所以我们在执行 make 的时候,其实是在执行 other 所对应的命令。

我们将当前目录下的 test.txt 文件删除,再次执行 make,运行结果如下所示: 

自动生成依赖关系

此时 make 查找不到对应的文件,所以首先会产生一个警告,然后去查找当前 makefile 中是否存在对应的目标,当前 makefile 中存在 test.txt 目标,所以会执行目标所对应的命令,这时顶层的目标为 all,所以又会去执行 all 所对应的命令。

makefile 中命令的执行机制

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

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

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

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

下面的代码想要实现功能?有没有问题?

自动生成依赖关系

每个命令都是 Shell 开启一个新的进程执行的,进程之间不会相互影响,所以 subtest 是创建在当前目录下的,而不是 test 目录下的。 

makefile 的命令执行

makefile


.PHONY : all

all :
	set -e; \
	mkdir test; \
	cd test; \
	mkdir subtest

set -e 指定发生错误后立即退出执行,并通过接续符将四个命令组合成一个命令,Shell 此时只会创建一个进程去执行这些命令,所以 subtest 是创建在 test 目录下的。

解决方案的初步思路

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

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

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

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

解决方案原型

makefile


.PHONY : all clean

CC := gcc
MKDIR := mkdir
RM := rm -rf

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)

-include $(DEPS)

all :
	@echo "this is $@"

%.dep : %.c
	@echo "creating $@ ..."
	@set -e; \
	$(CC) -MM -E $^ | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@	

clean :
	$(RM) $(DEPS)

.dep 依赖文件记录了一个目标文件所对应的依赖关系;如果对应的依赖文件不存在,则通过 include 去执行目标所对应的命令,gcc -MM 生成目标文件的依赖关系,通过 sed 将这些依赖关系都添加 objs/ 前缀,并重定向到对应的依赖文件中。

上一篇:OpenFeign源码分析二


下一篇:CentOS 6.3 FTP 安装vsftp 虚拟用户设置全解