前言

Makefile自动生成头文件依赖是很常用的功能,本文的目的是想尽量详细说明其中的原理和过程。

Makefile模板

首先给出一个本人在小项目中常用的Makefile模板,支持自动生成头文件依赖。

  1. CC = gcc
  2. CFLAGS = -Wall -O
  3. INCLUDEFLAGS =
  4. LDFLAGS =
  5. OBJS = seq.o
  6. TARGETS = test_seq
  7. .PHONY:all
  8. all : $(TARGETS)
  9. test_seq:test_seq.o $(OBJS)
  10. $(CC) -o $@ $^ $(LDFLAGS)
  11. %.o:%.c
  12. $(CC) -o $@ -c $< $(CFLAGS) $(INCLUDEFLAGS)
  13. %.d:%.c
  14. @set -e; rm -f $@; $(CC) -MM $< $(INCLUDEFLAGS) > $@.$$$$; \
  15. sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
  16. rm -f $@.$$$$
  17. -include $(OBJS:.o=.d)
  18. .PHONY:clean
  19. clean:
  20. rm -f $(TARGETS) *.o *.d *.d.*

基础知识

在进行下一步之前,首先需要了解make的执行步骤:

  1. 读入Makefile
  2. 读入被include的其它Makefile
  3. 初始化Makefile中的变量
  4. 推导隐晦规则,并分析所有规则
  5. 为所有目标创建依赖关系链
  6. 根据依赖关系,决定哪些目标需要重新生成
  7. 执行生成命令

如何动态生成依赖关系?

从上面make的执行过程中可看出,要动态生成依赖关系,只能利用第2步读入其它Makefile的机制。那么,我们是否可以先把生成的依赖关系保存到文件,然后再把该文件的内容包含进来?
答案是Yes! 只要利用include的机制。

include关键字是用于读入其它Makefile文件。当该文件不存在时,make会寻找是否有生成它的规则,如果有,则执行其生成命令,然后再尝试读入。在include前加减号"-"可以上make忽略其产生的错误,并不输出任何错误信息。

即是说,我们需要提供生成规则文件的规则。例如,我们可以这样动态生成头文件依赖关系:

  1. seq.d : seq.c
  2. @echo seq.o seq.d : seq.c seq.h" > $@
  3. -include seq.d

当make执行时,Makefile中的内容将是这样子(指内存上的数据):

  1. seq.d : seq.c
  2. @echo seq.o seq.d : seq.c seq.h" > $@
  3. seq.o seq.d : seq.c seq.h

特别注意的是,由于对seq.c和seq.h的修改需要更新seq.d的内容(因为依赖关系可能已变化),因此seq.d也要在依赖关系的目标列表中。

自动生成头文件依赖

基于上面的例子,现在可以开始讨论如何自动生成头文件依赖。

自动生成依赖关系

大多数c/c++编译器提供了-M选项,可自动寻找源文件依赖的头文件,并生成依赖规则。对于gcc,需要使用-MM选项,否则它会把系统依赖的头文件也包含进来。例如执行下面一个命令:

  1. gcc -MM seq.c

将输出:

  1. seq.o : seq.c seq.h

但我们需要结果是seq.d也要包含在目标列表中,所以还需要对它进行文本处理。因此,上面的例子可改为:

  1. seq.d : seq.c
  2. @set -e; \
  3. gcc -MM $< > $@.$$$$; \
  4. sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
  5. rm -f $@.$$$$
  6. -include seq.d

生成规则中的执行命令解释

第一个命令@set -e。@关键字告诉make不输出该行命令;set -e的作用是,当后面的命令的返回值非0时,立即退出。

那么为什么要把几个命令写在”同一行“(是对于make来说,因为\的作用就是连接行),并用分号隔开每个命令?因为在Makefile这样做才能使上一个命令作用于下一个命令。这里是想要set -e作用于后面的命令。

第二个命令gcc -MM $< > $@.$$$$, 作用是根据源文件生成依赖关系,并保存到临时文件中。内建变量$<的值为第一个依赖文件(那seq.c),$$$$为字符串"$$",由于makefile中所有的$字符都是特殊字符(即使在单引号之中!),要得到普通字符$,需要用$$来转义; 而$$是shell的特殊变量,它的值为当前进程号;使用进程号为后缀的名称创建临时文件,是shell编程常用做法,这样可保证文件唯一性。

第三个命令作用是将目标文件加入依赖关系的目录列表中,并保存到目标文件。关于正则表达式部分就不说了,唯一要注意的是内建变量$*$*的值为第一个依赖文件去掉后缀的名称(这里即是seq)。

第四个命令是将该临时文件删除。

如果把内建变量都替换成其值后,实际内容是这样子:

  1. seq.d : seq.c
  2. @set -e; \
  3. gcc -MM seq.c > seq.d.$$$$; \
  4. sed 's,\(seq\)\.o[ :]*,\1.o seq.d : ,g' < seq.d.$$$$ > seq.d; \
  5. rm -f seq.d.$$$$
  6. -include seq.d

Makefile的模式匹配

最后,再把Makefile的模式匹配应用上,就完成自动生成头文件依赖功能了:

  1. %.d : %.c
  2. @set -e; \
  3. gcc -MM $@ > $@.$$$$; \
  4. sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
  5. rm -f $@.$$$$
  6. -include seq.d

参考资料

<跟我一起写Makefile> by 陈晧
GNU make官方文档 http://www.gnu.org/software/make/manual/make.html

Makefile自动生成头文件依赖的更多相关文章

  1. Makefile中自动生成头文件依赖

    为什么需要自动生成头文件依赖? 编译单个源文件时,需要获取文件中包含的头文件的信息,但是一般的Makefile不会在规则中明确写明文件依赖的头文件,所以单独修改头文件后,不会导致包含头文件的源文件重新 ...

  2. makefile自动生成目标与依赖的关系

    有main.c: #include <stdio.h> #include "command.h" int main(int argc, const char *argv ...

  3. pycharm自动生成头文件注释

    1.在file->settings->file and code templates->python script即可自定制pycharm创建文件自动生成的头文件注释信息 2.创建p ...

  4. makefile自动生成依赖关系

    手工编写依赖关系不仅工作量大而且极易出现遗漏,更新也很难及时,修改源或头文件后makefile可能忘记修改.为了解决这个问题,可以用gcc的-M选项自动生成目标文件和源文件的依赖关系.-M选项会把包含 ...

  5. 用shell脚本新建shell文件并自动生成头说明信息

    目标: 新建文件后,直接给文件写入下图信息 代码实现: [root@localhost test]# vi AutoHead.sh #!/bin/bash#此程序的功能是新建shell文件并自动生成头 ...

  6. 用shell脚本新建文件并自动生成头说明信息

    目标: 新建文件后,直接给文件写入下图信息 代码实现: [root@localhost test]# vi AutoHead.sh #!/bin/bash #此程序的功能是新建shell文件并自动生成 ...

  7. CPLUSPLUS 获得 一个源文件的头文件依赖。即该文件所需要的所有头文件

    核心命令:gcc -M *.h.*.cpp 转: 自动处理头文件的依赖关系 http://blog.csdn.net/su_ocean16/article/details/5374696 现在我们的M ...

  8. makefile自动生成学习

    https://www.cnblogs.com/jrglinux/p/6964169.html 关键是如何写Makefile.am  其他的交给 自动工具完成 添加一个 很好的博客 学习下 https ...

  9. 程序自动生成Dump文件

    前言:通过drwtsn32.NTSD.CDB等调试工具生成Dump文件, drwtsn32存在的缺点虽然NTSD.CDB可以完全解决,但并不是所有的操作系统中都安装了NTSD.CDB等调试工具.了解了 ...

随机推荐

  1. iOS-开发日志-UIButton

    UIButton属性 1.UIButton状态: UIControlStateNormal          // 正常状态    UIControlStateHighlighted     // 高 ...

  2. 添加PATH

    在Linux CentOS系统上安装完php和MySQL后,为了使用方便,需要将php和mysql命令加到系统命令中,如果在没有添加到环境变量之前,执行“php -v”命令查看当前php版本信息时时, ...

  3. JavaScript入门介绍(一)

    JavaScript入门介绍 [经常使用的调试工具][w3school.com.cn在线编辑] [Chrome浏览器 开发调试工具]按F121.代码后台输出调试:console.log("t ...

  4. (转载)StringGrid常用属性和常用操作

    Delphi StringGrid常用属性和常用操作 StringGrid组件用于建立显示字符串的网格,与电子表格相似.它可使表格中的字符串和相关对象操作简单化.StringGrid组件提供了许多可控 ...

  5. How to get Directory size in IsolatedStorage of Windows Phone 8 App

    There is no API to get the total size of a specific directory in the isolated storage. Therefore, th ...

  6. tpl demo

    using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Ta ...

  7. 写一个TT模板自动生成spring.net下面的配置文件。

    这个是目标. 然后想着就怎么开始 1.

  8. Java NIO之Selector

    选择器是JavaNIO重磅推出的一个概念:在旧有的系统中为了跟踪多端口消息,需要为每一个端口配备一个线程做监听:但是有了selector就不需要了,一个Selector可以管理一众渠道(channel ...

  9. 详解C/C++函数指针声明

    要理解一个C程序,仅仅理解组成该程序的符号是不够的.程序员还必须理解这些符号是如何组合成声明.表达式.语句和程序的. 我们先来看看下面的一个语句: 1 ( *( void(*)())0)(); 这是当 ...

  10. (转) 各种好用的插件 Xcode

    时间就是金钱.编码效率的提升意味着更多的收入.可是当我们的开发技巧已经到达一定高度时,如何让开发效率更上一层楼呢?答案就是使用开发工具!在这篇文章中,我会向你介绍一些帮助我提升编码速度和工作效率的工具 ...