makeifle之依赖的类型

来源:互联网 发布:linux打开oracle 编辑:程序博客网 时间:2024/04/28 18:27

规则中依赖(prerequisites)的分类

GNU make规则中的依赖(prerequisites)可以分为两类。

第一类:也是最常见的依赖类型,我们称之为常规依赖normal prerequisites)。

第二类:此依赖类型比较特殊,称之为order-only依赖order-only prerequisites)。


常规依赖

常规依赖理解起来很容易:一个规则中的常规依赖主要告诉make两件事。
第一,它决定了重建此规则目标所要执行规则(确切的说是执行命令)的顺序。换句话说,就是在更新这个规则的目标(执行此规则的命令)之前需要按照什么样的顺序来执行具体的规则(命令)来重建这些依赖文件(对所有依赖文件的重建,使用显式或者隐式规则)。
举例来说,对于这样的一个规则:A: B C,那么在重建目标A之前,首先需要完成对它的依赖B和C的重建。重建B和C的过程就是执行Makefile中以B和C为目标的规则)。
第二,它确定了一个依存关系;规则中如果依赖文件的任何一个比目标文件新,则认为规则的目标已经过期而需要重建目标文件。


order-only依赖

常规依赖在大多数情况下已经能满足我们的需求。那么什么是order-only依赖,它又什么用呢?

有时,需要定义一个这样的规则,在更新目标(目标文件已经存在)时只需要根据依赖文件中的部分来决定目标是否需要被重建,而不是在依赖文件的任何一个被修改后都重建目标。为了实现这一目的,相应的就需要对规则的依赖进行分类,一类是在这些依赖文件被更新后,需要更新规则的目标;另一类是更新这些依赖的,可不需要更新规则的目标。我们把第二类称为:order-only依赖。书写规则时,order-only依赖使用管道符号“|”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是order-only依赖。这样的规则书写格式如下:

    target: normal-dep1 normal-dep2 | order-dep1 order-dep2
            command
一起来看一个简单的例子,就很好理解了。如下:

依据Makefile很容易得知依存关系如下:

第一次运行该Makefile,结果如下:
# make 
making prereq1
touch prereq1
making prereq0
touch prereq0
making prereq2
touch prereq2
making target
touch target

跟预期的完全一样。接下来,我们先更新prereq1的时间戳,然后再运行make
# touch prereq1
# make
making target
touch target

也和预期的一致,因为prereq1是常规依赖,所以现在prereq1更新了,自然要更新target。
但是如果现在我们更新prereq2呢? 会是怎样?
# touch prereq2
# make
make: Nothing to be done for 'all'.
哈哈,看到没有,make什么都不做。如果是更新prereq0呢?
# touch prereq0
# make 
making prereq2
touch prereq2

看到没? 只更新了prereq2,但是也并没有更新target。

想必通过这个例子,就能够完全明白order-only依赖了。
那order-only依赖在实际的工程中有什么实际的用处吗? 当然有,接下来就来隆重介绍一下。
先来了解一个好的工程习惯:擅用多目录来管理你的工程
在绝大多数情况下,我们并不希望生成的文件(包括目标文件*.o、静态库*.a、共享库*.so以及可执行文件)与源码文件混合在一起。因为一旦混合在一起,单目录下的文件数量就会增倍,这不仅导致文件混乱,还大大降低了源码的结构性。如果有意将生成的文件与源码分开的话,源码结构就能保持简单,一目了然,甚至有利于最后的打包发布。
为了更好的说明问题,具体来看一个简单的例子。工程结构如下:

如果makefile写成如下会有什么问题呢?

如果真实地运行makefile,就会得到如下错误信息:
# make
gcc -Wall -O2 -Iinclude -c -o objdir/foo.o foo.c
Assembler messages:
Fatal error: can't create objdir/foo.o: No such file or directory
Makefile:18: recipe for target 'objdir/foo.o' failed
make: *** [objdir/foo.o] Error 1

这是因为编译器不能往不存在的路径中写信息造成的。因此我们应该在任何.o还没生成之前生成$(OBJDIR)目录。修改makefile如下:

可以看到这次为$(OBJS)增加了一个常规依赖$(OBJDIR),这样会不会有问题呢?实际运行下就知道了。
# make
mkdir -p objdir
gcc -Wall -O2 -Iinclude -c -o objdir/foo.o foo.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc -Wall -O2 -Iinclude -c -o objdir/bar.o bar.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc -Wall -O2 -Iinclude -c -o objdir/main.o main.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc   -o simpletest objdir/foo.o objdir/bar.o objdir/main.o

这次果然是在生成任何.o文件之前创建了%(OBJDIR)目录,这是我们所期望的。最终也成功生成了我们的可执行文件,但是发现没有,编译过程中一直伴随这奇怪的警告信息?这是因为现在$(OBJS)的依赖中增加了$(OBJDIR),因此$^会将其包含中其中。解决方法就是使用$(OBJDIR)变成order-only依赖。终极版本makefile如下:

此外,还有一点需要知道的是,在每次生成一个新的.o文件时,$(OBJDIR)目录的时间戳会更新的。如果$(OBJDIR)不是order-only依赖的话,势必导致make每次会检查它的时间戳,从而重新编译目标文件。可能上面的makefile还看不出问题所在,让我们修改一下makefile如下:

运行以下
# make 
make clean
make[1]: Entering directory '/tmp/makefile_learn'
rm -rf objdir simpletest
make[1]: Leaving directory '/tmp/makefile_learn'
make simpletest
make[1]: Entering directory '/tmp/makefile_learn'
mkdir -p objdir
gcc -Wall -O2 -Iinclude -c -o objdir/foo.o foo.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc -Wall -O2 -Iinclude -c -o objdir/bar.o bar.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc -Wall -O2 -Iinclude -c -o objdir/main.o main.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc   -o simpletest objdir/foo.o objdir/bar.o objdir/main.o
make[1]: Leaving directory '/tmp/makefile_learn'
make test
make[1]: Entering directory '/tmp/makefile_learn'
gcc -Wall -O2 -Iinclude -c -o objdir/foo.o foo.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc -Wall -O2 -Iinclude -c -o objdir/bar.o bar.c objdir
gcc: warning: objdir: linker input file unused because linking not done
gcc   -o simpletest objdir/foo.o objdir/bar.o objdir/main.o
./simpletest
foo
bar
make[1]: Leaving directory '/tmp/makefile_learn'

会发现尽然编译了两次,这是因为在make test时,发现$(OBJDIR)的时间戳比foo.o和bar.o新,所以重新编译了它们。如果改成order-only依赖则不存在这个问题。
本文over~~

参考地址:
http://blog.kompiler.org/2015/06/11/separate-build-and-source-directories-in-makefiles/《Separate build and source directories in Makefiles》
http://www.cmcrossroads.com/article/making-directories-gnu-make 《Making directories in GNU Make 》

1 0
原创粉丝点击