Makefile 8——使用依赖关系文件

Posted 悄然拔尖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Makefile 8——使用依赖关系文件相关的知识,希望对你有一定的参考价值。

Makefile中存在一个include指令,它的作用如同C语言中的#include预处理指令。在Makefile中,可以通过include指令将自动生成的依赖关系文件包含进来,从而使得依赖关系文件中的内容成为Makefile的一部分。

在此之前,先介绍一下Makefile中的include的用法。

 1 .PHONY:all clean
 2 DIR_DEP=dep
 3 DEPS=test_deps
 4 all: exe
 5 
 6 include $(DEPS)
 7 
 8 dep:
 9     mkdir dep
10 exe:
11     @echo "exe"
12 
13 test_deps:$(DIR_DEP)
14     @echo "deps"

 好好分析上图的运行结果,能让自己更好地理解后面的东西。
 
include”指示符告诉 make 暂停读取当前的 Makefile,而转去读取“ include”
指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。 Makefile 中指示符
“ include”书写在独立的一行 。
通常指示符“include”用在以下场合:

1. 有多个不同的程序,由不同目录下的几个独立的Makefile来描述其重建规则。它
们需要使用一组通用的变量定义或者模式
规则。通用的做法是将这些共同使用的变量或
者模式规则定义在一个文件中(没有具体的文件命名限制),在需要使用的
Makefile中使用指示符“include”来包含此文件。
2. 当根据源文件自动产生依赖文件时;我们可以将自动产生的依赖关系保存在另
外一个文件中,主Makefile使用指示符“include”包含这些文件。这样的做法
比直接在主Makefile中追加依赖文件的方法要明智的多。其它版本的make已经
使用这种方式来处理。
如 果 指 示 符 “ include ” 指 定 的 文 件 不 是 以 斜 线 开 始 ( 绝 对 路 径 , 如
/usr/src/Makefile...),而且当前目录下也不存在此文件; make将根据文件名试图在以下
几个目录下查找:首先,查找使用命令行选项“-I”或者“--include-dir”

指定的目录,如果找到指定的文件,则使用这个文件;否则继续
依此搜索以下几个目录(如果其存在):“/usr/gnu/include”、“/usr/local/include”和
“/usr/include”。
当在这些目录下都没有找到“include”指定的文件时,make将会提示一个包含文
件未找到的告警提示,但是不会立刻退出。而是继续处理Makefile的后续内容。当完成
读取整个Makefile后,make将试图使用规则来创建通过指示符“include”指定的但未
找到的文件(参考 3.7 makefile文件的重建 一节),当不能创建它时(没有创建这个文
件的规则),make将提示致命错误并退出。

通常我们在 Makefile 中可使用“-include”来代替“include”,来忽略由于包含文
件不存在或者无法创建时的错误提示(“-”的意思是告诉 make,忽略此操作的错误。
make 继续执行)。

我们改成-include之后:

这样就没有提示找不到那个目录或文件了,但是我们必须确保有规则去创建include指定的内容,否则最后将出错。

make的执行过程如下:
1. 依次读取变量“MAKEFILES”定义的makefile文件列表
2. 读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到那个就读取那个)
3. 依次读取工作目录makefile文件中使用指示符“include”包含的文件
4. 查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)
5. 初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支
6. 根据“终极目标”以及其他目标的依赖关系建立依赖关系链表
7. 执行除“终极目标”以外的所有的目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件)
8. 执行“终极目标”所在的规则

知道了include优先于本Makefile的目标运行之后,来看我们的complicated项目:

 1 .PHONY: all clean
 2 
 3 MKDIR = mkdir
 4 RM = rm
 5 RMFLAGS = -rf
 6 
 7 CC=gcc
 8 
 9 DIR_OBJS=objs
10 DIR_EXES=exes
11 DIR_DEPS=deps
12 
13 DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
14 
15 EXE=complicated
16 EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
17 SRCS=$(wildcard *.c)
18 OBJS=$(SRCS:.c=.o)
19 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
20 DEPS=$(SRCS:.c=.dep)
21 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))
22 
23 all: $(EXE)
24 
25 include $(DEPS)
26 
27 $(DIRS):
28     $(MKDIR) $@
29 $(EXE):$(DIR_EXES) $(OBJS)
30     $(CC) -o $@ $(filter %.o,$^)
31 $(DIR_OBJS)/%.o:$(DIR_OBJS) %.c 
32     $(CC) -o $@ -c $(filter %.c,$^)
33 $(DIR_DEPS)/%.dep:$(DIR_DEPS) %.c
34     @echo "Creating $@ ..."
35     @set -e;\\
36     $(RM) $(RMFLAGS) $@.tmp;\\
37     $(CC) -E -MM $(filter %.c,$^) > $@.tmp;\\
38     sed \'s,\\(.*\\)\\.o[:]*,objs/\\1.o:,g\' <$@.tmp >$@;\\
39     $(RM) $(RMFLAGS) $@.tmp
40 clean:
41     $(RM) $(RMFLAGS) $(DIRS) 

这里增加了filter函数,具体可以看前面函数那一篇随笔。正如前面所提及的,当make看到include指令时会试图去构建所需包含进来的依赖文件,这样就不必在显式地让all目标依赖它了。这也是我举第一个例子的原因,有了include,make会自动去 构建 依赖。所以,在complicated项目中,我们在每一个依赖项之前都添加了一个先决条件,这个先决条件就是每一个依赖的目录。

 

需要指出地是,上面的代码可能会无限循环。

如果你的编译器安装在FAT32文件系统上,将可以运行不会无限循环,但是如果是在NTFS文件系统上,会死循环。笔者的Linux上是无限循环了。

出现无限循环的原因和文件系统有关,有的文件系统当目录中的文件被更改时,目录时间戳随之更改,由于在Makefile中创建依赖关系时,制定了deps目录是其第一个先决条件,于是,deps目录时间戳地改变使得make又一次使用规则再次创建main.dep 和foo.dep,这样造成了无限循环。

既然发现了问题,证明我们这个Makefile存在bug,需要更改,基本思路是:

如果deps目录不存在,则让deps目录成为规则的第一个先决条件;

如果deps目录已经存在,则不让deps目录出现在规则的先决条件中。

沿着这个思想走下去,需要用到Makefile中的条件语法。

 关键字“ ifeq
此关键字用来判断参数是否相等,格式如下:
`ifeq (ARG1, ARG2)
`ifeq \'ARG1\' \'ARG2\'\'
`ifeq "ARG1" "ARG2"\'
`ifeq "ARG1" \'ARG2\'\'
`ifeq \'ARG1\' "ARG2"\'
替换展开“ ARG1”和“ ARG1”后,对它们的值进行比较。如果相同则(条件为
真)将“ TEXT-IF-TRUE”作为 make 要执行的一部分,否则将“ TEXT-IF-FALSE”作
make 要执行的一部分(上边的第二种格式)。

还有ifdef,ifndef和ifeq,ifneq用法类似。

关键字“ ifdef
关键字“ ifdef”用来判断一个变量是否已经定义。格式为:
`ifdef VARIABLE-NAME\'
如果变量“ VAEIABLE_NAME”的值非空(在 Makefile 中没有定义的变量的值为空),
那么表达式为真,将“ TEXT-IF-TRUE”作为 make 要执行的一部分。否则,表达式为
假,如果存在“ TEXT-IF-FALSE”,就将它作为 make 要执行一部分。当一个变量没有
被定义时,它的值为空。“ VARIABLE-NAME”可以是变量或者函数的引用。
对于“ ifdef”需要说明的是: ifdef 只是测试一个变量是否有值,不会对变量进行
替 换 展 开 来 判 断 变 量 的 值 是 否 为 空 。 对 于 变 量 “ VARIABLE-NAME ” , 除 了
VARIABLE-NAME=”这种情况以外,使用其它方式对它的定义都会使“ ifdef”返回
真。就是说,即使我们通过其它方式(比如,定义它的值引用了其它的变量)给它赋了
一个空值,“ ifdef”也会返回真。我们来看一个例子:
1
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
2
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
1 中的结果是:“ frobozz = yes”;而例 2 的结果是:“ frobozz = no”。其原因就
是在例 1 中,变量“ foo”的定义是“ foo = $(bar)”。虽然变量“ bar”的值为空,但是
ifdef”判断的结果是真。因此当我们需要判断一个变量的值是否为空的情况时,需要
使用“ ifeq”(或者“ ifneq”)而不是“ ifdef”。

 运用条件语法后的Makefile如下所示:

 

 1 .PHONY: all clean
 2 
 3 MKDIR = mkdir
 4 RM = rm
 5 RMFLAGS = -rf
 6 
 7 CC=gcc
 8 
 9 DIR_OBJS=objs
10 DIR_EXES=exes
11 DIR_DEPS=deps
12 
13 DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
14 
15 EXE=complicated
16 EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
17 SRCS=$(wildcard *.c)
18 OBJS=$(SRCS:.c=.o)
19 OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
20 DEPS=$(SRCS:.c=.dep)
21 DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS))
22 
23 ifeq ("$(wildcard $(DIR_OBJS))","")
24 DEP_DIR_OBJS :=$(DIR_OBJS)
25 endif#dir_objs
26 ifeq ("$(wildcard $(DIR_EXES))","")
27 DEP_DIR_EXES :=$(DIR_EXES)
28 endif#dir_exes
29 ifeq ("$(wildcard $(DIR_DEPS))","")
30 DEP_DIR_DEPS :=$(DIR_DEPS)
31 endif#dir_deps
32 
33 all: $(EXE)
34 
35 include $(DEPS)
36 
37 $(DIRS):
38     $(MKDIR) $@
39 $(EXE):$(DEP_DIR_EXES) $(OBJS)
40     $(CC) -o $@ $(filter %.o,$^)
41 $(DIR_OBJS)/%.o:$(DEP_DIR_OBJS) %.c 
42     $(CC) -o $@ -c $(filter %.c,$^)
43 $(DIR_DEPS)/%.dep:$(DEP_DIR_DEPS) %.c
44     @echo "Creating $@ ..."
45     @set -e;\\
46     $(RM) $(RMFLAGS) $@.tmp;\\
47     $(CC) -E -MM $(filter %.c,$^) > $@.tmp;\\
48     sed \'s,\\(.*\\)\\.o[:]*,objs/\\1.o:,g\' <$@.tmp >$@;\\
49     $(RM) $(RMFLAGS) $@.tmp
50 clean:
51     $(RM) $(RMFLAGS) $(DIRS) 

 

 

这样就不会无限循环了,同样,如果不想make报那个警告没有什么那个文件或目录,在Makefile中的include加上符号 ’-‘,这个提示信息在这里是安全的,因为make就是这样设计的include指令。 改动主要是增加了三个变量,这三个变量的值根据相应的目录是否存在而分别赋值。如果不存在,就将目录名赋值给它,如果存在,则这三个变量的值为空,在Makefile中,就算没有定义一个变量,直接$(变量),此时变量为空,增加的三个变量都作为对应规则中的第一个先决条件,这样无限循环问题得到了解决。

但是,我很纳闷,为什么加了条件语法,这个死循环的问题就解决了?就算加了条件语法,生成在dep目录下的文件依旧会在我的文件系统上改变时间戳,那样还是会一直循环啊,为什么这里却得到了解决?这就要到最前面的Makefile中说起了,时间戳改变,make会去重新构建时间戳改变了的所以依赖内容,有了条件语句之后,就算时间戳改变了,但是条件语句会生成对应地条件来阻止无限循环,虽然条件语句没有放在类似C语言的while(1)这种无限循环的语句块中,但是时间戳的改变,会让make重新构建和时间戳改变文件的所以依赖,这样条件语句相当于是一直在while(1)中一样。最后说明的是,条件语句很像C语言中的条件编译,它应该在构建目标之前预编译,所以通常条件语法应该位于目标之前,如果放在目标执行完毕之后,条件语句将失去作用,毕竟条件语句是要去控制目标和依赖项的,位于它们之前也是理所当然的,Makefile中语句的大体运行顺序在上面有给出。

 

1 ifeq ($(wildcard $(DIR_OBJS)),)
2 DEP_DIR_OBJS :=$(DIR_OBJS)
3 endif#dir_objs
4 ifeq ($(wildcard $(DIR_EXES)),)
5 DEP_DIR_EXES :=$(DIR_EXES)
6 endif#dir_exes
7 ifeq ($(wildcard $(DIR_DEPS)),)
8 DEP_DIR_DEPS :=$(DIR_DEPS)
9 endif#dir_deps

 

也可以用上面的代替之前Makefile中的三个变量部分,之前用的空“”,括号中还加了引号“,其实按照GNU_MAKE上的示例,用的ifeq(XX,)表示如果XX为空就...

 

以上是关于Makefile 8——使用依赖关系文件的主要内容,如果未能解决你的问题,请参考以下文章

如何通过自动生成的makefile 看各个文件的依赖关系

为啥这个makefile不构建依赖关系,而只是在使用变量时

Makefile

协议缓冲区文件的 Makefile 自动依赖关系问题

Makefile - 仅当文件不存在时才建立依赖关系

Makefile 7——自动生成依赖关系 三颗星