GNU Make 自动生成依存(依赖)关系的完善解决方案

GNU Make 自动生成依存(依赖)关系的完善解决方案

背景

最近在做stm32在Linux上的工程模板,以此学习Makefile、Git、链接脚本和Shell脚本的应用能力,也的确发现了不少被IDE隐藏在背后的东西,总想和大家分享点什么。
因为是首次自己编写Makefile,在它身上消耗不少精力,但因为都是些小问题,值得拿出来和大家分享的也不多,而依存关系的解决方案却是其中之一。原因很简单,我没在网上找到完善的解决方案——起码在中文资料中没有。
当然,这个解决方案也不是我想到的,是在GNU Make项目管理(第三版)中学到的。
注:本文涉及代码在GNU Make项目管理(第三版)中可以找到,但不是完全相同,原文程序可能因环境不同不能在我的环境中运行,运行环境在后文注明。

提出问题

为什么要有依存关系

当我们在项目中重构头文件后,我们习惯性的make一下。
如果这个头文件没有在你的依存关系中,Make就会有以下错误的反应。
make: Nothing to be done for 'all'.
这错不能怪Make,是我们没有添加依存,打开Makefile添加关于该头文件的依存即可。

为什么要自动解决依存关系

随着代码量的增大,文件的增多,现在有几十个或者上百个文件需要编译,而每个文件都需要有自己的依存关系,这个依存关系也在随着项目的前进而不断修改,手工建立依存显得越来越不实际。
或者我们需要些工具来自动解决这个问题!

解决问题

Make的解决方案

-include $(SOURCES:.c=.d)
%d: %c
      @echo "create depend"
      $(CC) -MM $(CFLAGS) $< > $@.$$$$; \
      sed 's,\($*\)\.o[ :]*,\1.o $@ ,g' < $@.$$$$ > $@; \
      $(RM) $@.$$$$

以上是GNU Make帮助中给出的解决思路,我见到它的时候一头雾水,而现在也不想一句一句分析它,让我们修改一下,使它更易懂

简单修改

#我们使用include来包含依赖文件
-include $(SOURCES:.c=.d)
#如果查不到.d就用以下规则更新
%.d: %.c
#     利用GCC的MM选项,生成依赖,格式如同这样'hello.o : stdio.h hello.h'
      $(CC) -MM $(CFLAGS) $< | \
#     使用管道将输出传到sed,使用替换的方式得到这样的格式'hello.o hello.d : stdio.h hello.h'
      sed 's,\($*\)\.o[ :]*,\1.o $@ ,g' > $@;

基础问题

  1. include是什么?
    它将后面的文件包含进这个Makefile
  2. $(SOURCES:.c=.d)在干什么?
    Make变量sources中每个文件名的.c替换为.d
  3. 我还要创建.d?
    当然不,它由%.d: %.c规则产生
  4. -MM选项是什么?
    这个选项使GCC产生依赖规则,格式如同hello.o : stdio.h hello.h
  5. 那个sed在干什么?
    为了得到这样的格式hello.o hello.d : stdio.h hello.h
  6. 冒号前面两个文件是怎么回事?
    hello.o hello.d : stdio.h hello.h
    上面这个完全等价与下面这个
    hello.o : stdio.h hello.h
    hello.d : stdio.h hello.h
  7. 这样就有两个.o的依赖关系了,它们之间有什么关系?
    依赖关系可以有多个叠加,但规则只能有一个,如
    hello.o : hello.c
    $(CC) $(CFLAGS) $< -o $@
    hello.0 : stdio.h hello.h
    是可以的,但
    hello.o : hello.c
    $(CC) $(CFLAGS) $< -o $@
    hello.0 : stdio.h hello.h
    $(CC) $(CFLAGS) $< -o $@
    是不可以的,规则无法叠加。

自动生成依存关系原理

make扫描Makefile文件,如果要包含的.d文件不存在,就运行.d生成规则,生成.o的依赖关系,并生成.o。
在之后Make时发现.c或者.h文件被修改,就会更新.o文件,并重新生成规则文件.d,以保证下次Make时能检测到修改。

发现小BUG

之前那两个版本的依存解决方案,当然可以用,只是总出一些小BUG让人心烦。
以下内容摘自GNU Make 项目管理(第三版)(中译版)

1.没有效率。当发现某个依存文件不存在或尚未更新时,他就会更新.d文件并且重启自己。如果在makefile的读取期间make会进行许多工作并分析依存图,则重新读取makefile完全没有效率可言。
2.第一次建立工作目标。以及每当你加入新的源文件的时候,make都会产生警告信息。以上的状况都会让依存文件关联到尚不存在的新源文件上,所以当make试图读取依存文件的时候,会在产生依存文件之前先产生警告信息。这并不是无可挽回的醋无,不过相当烦人。
3.如果移除了一个源文件,make将会在随后的编译过程中发生无可挽回的错误并且停止运行。在此状况下会存在一个依存文件,他的必要条件包含了已移除的文件。因为make无法找到已移除的文件,而且不知道如何编译它,所以make将会输出make: *** No rule to make target ****.h,needed by ****.d. Stop
此外,Make也无法重建依存文件,因为已经发生如上的错误了。要解决此问题,只能靠手动移除该依存文件,但因为通常很难找到这些文件,所以用户一般会删除所有依存文件以及进行清理的编辑工作。当有文件被更名时,也会发生此错误。
请注意,将.c文件移除或更名所导致的问题并不如将头文件移除或更名所导致的问题显著。这是因为.c文件将会自动从依存文件列表中移除,而且不会在编译过程中发生错误。

完善规则

依旧引用,没办法,作者对GNU Make的理解让我这个半吊子难望其项背,但代码部分我会贴自己测试成功的

经过深思熟虑(作者)之后,我们发现没有必要重新启动make(即重新读取makefile)。如果我们说有一个依存文件要更新,这表示他的必要条件中至少有一个已被更改。也就是说我们必须更新工作目标。我们还发现这次执行make的时候也不需要这么做,因为依存信息并不会改变make的行为。但我们需要更新依存文件,这样做是为了在下次make时仍拥有完整的依存信息。
因为在这次执行make的时候,我们还不需要依存文件,所以我们可以在更新工作目标的同时产生依存文件。我们可以将编译规则改写成同时更新依存文件来完成这件事。

#这里我们使用一个函数来保证这段代码的灵活性,以便之后继续添加或修改
define make-depend
    $(CC) -M $(COMMONFLAGS) $1 | $(SED) 's,\($(notdir $2)\) *:,$2: ,' > $3
endef
%.o:%.c
    $(call make-depend,$<,$@,$(subst .o,.d,$@))
    $(CC) $(CFLAGS) -c $< -o $@

删除了%.d:%.c模式规则,以避免产生两次依存文件
排除对不产生最终目标的make和make clean引用依存文件的情况,提升效率。
在include前加入-或s以解决依存文件尚不存在时产生的警告信息

ifneq "$(MAKECMDGOALS)" "clean"
-include $(DeviceSrc:.c=.d)
endif

最后,当发现缺少必要条件时,有个小诀窍可避免产生警告信息。这个诀窍就是为缺少的文件建立一个没有必要条件、没有命令的工作目标。例如,假设我们的依存文件产生器建立了如下的依存关系:
hello.o : hello.h
现在假设:由于程序代码重构,hello.h不再存在。当我们再次运行makefile的时候,我们将会得到如下的错误:
make: *** No rule to make target hello.h, needed by hello.d. Stop.
但如果我们为hello.h新建一个没有命令的工作目标,这个错误就不会发生了
hello.o : hello.h
hello.h :
这是因为,如果hello.h不存在,他只会被视为尚未更新,任何以它为必要条件的工作目标将会被更新。所以,即使没有hello.h也可以产生依存文件。如果hello.h存在,则make将会把它视为已更新并且继续运行。所以我们只需让每个必要条件关联到一个空规则就行了。下面是加入新工作目标的make-depend版本:

define make-depend
    $(CC) -M $(COMMONFLAGS) $1 | $(SED) 's,\($(notdir $2)\) *:,$2: ,' > $3
    $(SED) -e 's/#.*//' -e 's/^[^:]*: *//g' -e 's/ *\\$$//g' -e '/^$$/ d' -e 's/$$/ :    /g' $3 >> $3
endef
%.o:%.c
    $(call make-depend,$<,$@,$(subst .o,.d,$@))
    $(CC) $(COMMONFLAGS) -c $< -o $@
ifneq "$(MAKECMDGOALS)" "clean"
-include $(DeviceSrc:.c=.d)
endif

我们对依存文件执行新的sed命令,以便产生额外的规则。
这段sed程序代码将会进行以下五种转换
1. 删除注释
2. 删除工作目标文件以及随后的空格
3. 删除结尾的空格
4. 删除空行
5. 为每行的结尾加上一个冒号
如果新的sed命令读取以下输入:
#any comments
target.o target.d: prereq1 prereq2 prereq3 \
prereq4
将会得到以下输出
prereq1 prereq2 prereq3 :
prereq4 :
所以,make-depend会把这个新的输出附加到原来的依存文件中。
这样就不会发生No rule to make target的错误了。

运行环境

  • Linux内核版本:Ubuntu 16.04.1 LTS
  • 操作系统:Ubuntu
  • Ubuntu版本:Linux version 4.4.0-83-generic (buildd@lgw01-29) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #106-Ubuntu SMP Mon Jun 26 17:54:43 UTC 2017
  • make工具版本:GNU Make 4.1
  • 编译工具:arm-none-eabi
  • arm-none-eabi 版本:gcc version 4.9.3 20150529 (prerelease) (15:4.9.3+svn231177-1)

致谢

感谢GNU Make项目管理(第三版)这本书的作者Robert Mecklenburg,给我们提供了这么好的一本书籍。
感谢出版商O’REILLY,我的很多编程技能都是从你们的书中得到的。
感谢GNU Make项目管理(第三版)这本书的翻译公司东南大学出版社,没有你们的翻译我就看不懂这么好的一本书,略尴尬······

求赞

第一次在CSDN发博客,也不知道是不是赞。多支持一下谢谢。
如果大家有新想法或疑问也欢迎交流探讨。

声明

也不知道我这样大篇幅引用会不会有什么责任,如果这篇文章损害了您的权益,请联系我删除它。
欢迎转载,但请务必不要删除下面这句话。
本文出自mukuss的博客:http://blog.csdn.net/u013944565/article/details/76098636

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值