Makefile 7——自动生成依赖关系 三颗星
后面会介绍gcc获得源文件依赖的方法,gcc这个功能就是为make而存在的。我们采用gcc的-MM选项结合sed命令。使用sed进行替换的目的是为了在目标名前加上“objs/”前缀。gcc的-E选项,预处理。在生成依赖关系时,其实并不需要gcc编译源文件,只要预处理就可以获得依赖关系了。通过-E选项,可以避免生成依赖关系时gcc发出警告,以及提高依赖关系的生成效率。
现在,已经找到自动生成依赖关系的方法了,那么如何将其整合到我们complicated项目的Makefile中呢?自动生成的依赖信息不能直接出现在Makefile中,因为不能动态地改变Makefile中的内容,此时我们需要通过创建依赖关系文件的方式。假设依赖关系的文件以“.dep”结尾,因此我们新创建一个deps文件,用来存放依赖关系文件信息。
Makefile如下:
.PHONY: all clean MKDIR = mkdir
RM = rm
RMFLAGS = -rf CC=gcc DIR_OBJS=objs
DIR_EXES=exes
DIR_DEPS=deps DIRS =$(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE=complicated
EXE:=$(addprefix $(DIR_EXES)/,$(EXE))
SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)
OBJS:=$(addprefix $(DIR_OBJS)/,$(OBJS))
DEPS=$(SRCS:.c=.dep)
DEPS:=$(addprefix $(DIR_DEPS)/,$(DEPS)) all:$(DIRS) $(DEPS) $(EXE)
$(DIRS):
$(MKDIR) $@
$(EXE):$(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o:%.c
$(CC) -o $@ -c $^
$(DIR_DEPS)/%.dep:%.c
@echo "Creating $@ ..."
@set -e;\
$(RM) $(RMFLAGS) $@.tmp;\
$(CC) -E -MM $^ >$@.tmp;\
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS)
(这个Makefile废了不少力气才想明白。。。)
和之前的complicated项目的Makefile相比:
1,增加了deps文件夹
2,删除了目标文件创建规则中的foo.h依赖,并将规则中的$<变回了$^
3,增加了了DEPS变量用于存放文件
4,为all目标增加了$(DEPS)
5,增加了一个用于创建依赖关系问价你的规则。在这个规则中,使用了gcc的-E和-MM选项来获取依赖关系。在生成最终的依赖关系文件之前,使用了一个由$@.tmp表示的临时文件,且在依赖文件生成以后将其删除。set -e的作用是告诉shell,在生成依赖关系文件的过程中如果出现任何错误就直接退出。shell异常退出的最终表现就是make会告诉我们出错了,从而停止后续的make工作。如果不设置这一行,当构建依赖出错时,make还会继续后面的工作并最终出错,这并不是我们希望看到的。读者可以测试故意在源文件或者头文件中植入错误并去掉set -e选项观察make的行为和加上set -e有上面不同。
这里还有几个知识点需要补充。
1.对于规则中的每一条命令,make都是在一个新的shell上运行它的。
2.如果希望多个命令在同一个shell中运行,可以用“;”将这些命令连起来。
3.当命令很长时,可以用“\”将一个命令书写成多行。
为了更好的理解第一点,我们做一个实验。现假设需要创建一个test目录,然后在这个test目录下再创建一个subtest子目录。编写Makefile如下:
.PHONY:all
all:
@mkdir test
@cd test
@mkdir subtest
可以看到test和subtest是同级目录并非父子目录,然后用上面提到的知识点更改Makefile:
.PHONY:all
all:
@mkdir test;\
cd test;\
mkdir subtest
这样就可以达到目的了。不过你可能会想,为什么这里后面的cd和最后一个mkdir不需要在前面加上@呢?那么我们加上试试呢?
如果使用了分号“ ;”,表示命令在同一个shell中运行,而且使用“ \”链接一条命令,既然是一条命令,自然不能够识别后面的@cd或者@mkdir,因为最开始的mkdir使用@,让终端不显示执行的指令,后面的cd和mkdir是在前面操作的情况下进行的 ,此时,直接使用命令即可。
还有一个需要注意的地方:
如同
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;
3.命令与选项
sed命令告诉sed如何处理由地址指定的各输入行,如果没有指定地址则处理所有的输入行。
此处sed引用此博客, 参考链接:http://www.cnblogs.com/edwardlost/archive/2010/09/17/1829145.html
3.1 sed命令
命令 | 功能 |
a\ |
在当前行后添加一行或多行。多行时除最后一行外,每行末尾需用“\”续行 |
c\ | 用此符号后的新文本替换当前行中的文本。多行时除最后一行外,每行末尾需用"\"续行 |
i\ | 在当前行之前插入文本。多行时除最后一行外,每行末尾需用"\"续行 |
d | 删除行 |
h | 把模式空间里的内容复制到暂存缓冲区 |
H | 把模式空间里的内容追加到暂存缓冲区 |
g | 把暂存缓冲区里的内容复制到模式空间,覆盖原有的内容 |
G | 把暂存缓冲区的内容追加到模式空间里,追加在原有内容的后面 |
l | 列出非打印字符 |
p | 打印行 |
n | 读入下一输入行,并从下一条命令而不是第一条命令开始对其的处理 |
q | 结束或退出sed |
r | 从文件中读取输入行 |
! | 对所选行以外的所有行应用命令 |
s | 用一个字符串替换另一个 |
g | 在行内进行全局替换 |
w | 将所选的行写入文件 |
x | 交换暂存缓冲区与模式空间的内容 |
y | 将字符替换为另一字符(不能对正则表达式使用y命令) |
3.2 sed选项
选项 | 功能 |
-e | 进行多项编辑,即对输入行应用多条sed命令时使用 |
-n | 取消默认的输出 |
-f | 指定sed脚本的文件名 |
元字符 | 功能 | 示例 |
^ | 行首定位符 | /^my/ 匹配所有以my开头的行 |
$ | 行尾定位符 | /my$/ 匹配所有以my结尾的行 |
. | 匹配除换行符以外的单个字符 | /m..y/ 匹配包含字母m,后跟两个任意字符,再跟字母y的行 |
* | 匹配零个或多个前导字符 | /my*/ 匹配包含字母m,后跟零个或多个y字母的行 |
[] | 匹配指定字符组内的任一字符 | /[Mm]y/ 匹配包含My或my的行 |
[^] | 匹配不在指定字符组内的任一字符 | /[^Mm]y/ 匹配包含y,但y之前的那个字符不是M或m的行 |
\(..\) | 保存已匹配的字符 | 1,20s/\(you\)self/\1r/ 标记元字符之间的模式,并将其保存为标签1,之后可以使用\1来引用它。最多可以定义9个标签,从左边开始编号,最左边的是第一个。此例中,对第1到第20行进行处理,you被保存为标签1,如果发现youself,则替换为your。 |
& | 保存查找串以便在替换串中引用 | s/my/**&**/ 符号&代表查找串。my将被替换为**my** |
\< | 词首定位符 | /\<my/ 匹配包含以my开头的单词的行 |
\> | 词尾定位符 | /my\>/ 匹配包含以my结尾的单词的行 |
x\{m\} | 连续m个x | /9\{5\}/ 匹配包含连续5个9的行 |
x\{m,\} | 至少m个x | /9\{5,\}/ 匹配包含至少连续5个9的行 |
x\{m,n\} | 至少m个,但不超过n个x | /9\{5,7\}/ 匹配包含连续5到7个9的行 |
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;
现在来分解这个复杂表达式,首先,sed s表示我们想用一个字符串替换另一个字符串,这也是我们使用sed的原因,它的s命令就
可以达到这个效果。
's,\(.*\)\.o[:]*,objs/\1.o:,g'第一次分解,此时需要知道,单引号是一对的,即s前面的'和g后面的'是一个整体单引号,
这也是sed命令的基础,至于单引号和双引号有什么区别,可百度谷歌或者必应。(但是我之前测试的单引号和双引号并不是我搜索所显示的那样,后面再试试吧)
继续分解,s,中s是替换字符串的意思,这个在上面的表格中可以查询到,逗号,表示模式分隔符,在这种有/出现的字符串中,我们选择了逗号,作为分隔符号。
所以下一次分解应该倒下一个逗号处,
\(.*\)\.o[:]*,
这里首先看 .* 它表示匹配任意字符,\( \)是一个整体,也是通过上面的表格得到的,然后转义字符\和.o在一起,把.的作用(匹配除换行符的单个字符)变成普通的.(就是一个字符.),那么这一句话就是
操作字符串所有有.o的且在.0后面(可以有空格)匹配:的零个或多个字符串。
objs/\1.o:,g
这里要解释的是\1.o 这里用了转义字符\加上1,这表示什么呢?尤其是这个1,表示的就是前面\( \)内的字符串,这是组
的概念,如何知道是第几组呢?前面的第一个\(\)的就是第一组,用转义字符\1表示,依次类推。g在sed中表示行内全局替换
这样,我们做一个假设例子来说明。
abc.o : 用这个代表\(.*\)\.o[:]*
然后objs/\1.o:,g之后呢,abc.o :变成了 objs/abc.o: 这里相当于给前面的通用匹配加上了objs/前缀,并且把:和.o之前的空格去掉了
最后这个<$@.tmp >$@;这不属于sed的内容了,属于linux和Makefile的东西,$@.tmp重定向输入给前面的sed替换操作,
$@代表目标在Makefile中,$@.tmp是前面的Makefile生成的,<重定向,看方向是输入,
就是把$@.tmp重定向输入给sed,经过sed替换之后,再输出重定向 > 到$@,这个是目标。
这样再回过头去看之前那个Makefile就可以看懂了。
Makefile 7——自动生成依赖关系 三颗星的更多相关文章
- makefile自动生成依赖关系
手工编写依赖关系不仅工作量大而且极易出现遗漏,更新也很难及时,修改源或头文件后makefile可能忘记修改.为了解决这个问题,可以用gcc的-M选项自动生成目标文件和源文件的依赖关系.-M选项会把包含 ...
- Makefile 自动生成依赖
虽然以前对Makefile有个基本概念,但是真正到自己去写一个哪怕是简单的Makefile时也会遇到不少的麻烦. 现在我有如下文件 dList.h dList.c memory.c debug. ...
- Makefile中自动生成头文件依赖
为什么需要自动生成头文件依赖? 编译单个源文件时,需要获取文件中包含的头文件的信息,但是一般的Makefile不会在规则中明确写明文件依赖的头文件,所以单独修改头文件后,不会导致包含头文件的源文件重新 ...
- Makeflie自动生成依赖,自动化编译
在netbeans里开发,有一个重要文件makefile,是用来编译所有的文件. 项目的目录结构如下,扁平的目录结构,如何实现自动化编译,写makefile呢? 第一版 基础版: CC = g++ C ...
- make自动生成依赖文件的两种形式
最近编译源文件发现当修改头文件后,make并不会自动把包含此头文件的所有源文件重新编译,而每次都是需要把对应的中间文件清除才能重新编译,非常的麻烦.因此需要make自动对源文件所依赖的头文件进行管理, ...
- EF Core 2.0 已经支持自动生成父子关系表的实体
现在我们在SQL Server数据库中有Person表如下: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ) NULL, ) NULL, [Cr ...
- 关于makefile中自动产生依赖的理解
本博文是在学习了<GNU Make中文手册>后记录下来的自己的关于自动产生makefile依赖的语句的理解,向大家分享. <GNU make中文手册>中的相关章节见一下链接: ...
- IDEA maven项目查自动查看依赖关系,解决包冲突问题
在maven项目中找到pom.xml,打开. <dependencies> <dependency> <groupId>org.apache.storm</g ...
- Android Studio自动生成UML关系类图
android studio 根据源码自动生成UML的插件介绍http://www.jianshu.com/p/cbccd831cf01 simpleumlhttps://plugins.jetbra ...
随机推荐
- Redis学习(4)-数据类型,string,hash
Redis数据类型: redis使用键值对保存数据 key:全部是字符串 value:五种数据类型:string,hash,List,Set,有序的Set集合. key命名:自定义,名字不要过长,否则 ...
- 如何在MyEclipse中将项目部署Tomcat
如果系统里面还没有配置Tomcat,请参照<Windows下如何配置tomcat环境变量>. 1.新建一个web project项目.如下: 效果如下: 2.编写一个实现Servlet接口 ...
- HDUOJ----More is better(并查集)
More is better Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 327680/102400 K (Java/Others) ...
- razor----js
<script> $(document).ready(function () { // 2 直接加引号转换 var SpecialAptitude = '@Model.SpecialAp ...
- Python学习笔记014——生成器Generator
1 生成器定义 在Python中,一边循环一边计算的机制,称之为生成器(generator). 生成器是一个迭代器. 含有yield语句的函数是生成器函数,该函数被调用时返回一个生成器对象(yield ...
- 【Oracle错误集锦】:ORA-12154: TNS: 无法解析指定的连接标识符
相信这个错误大家都不陌生,仅仅要安装使用过Oracle的预计都遇到过这个问题,一般出如今用PL/SQL连接Oracle数据库的时候发生的. 导致这个错误的原因以及解决方式都是多种多样的,我也是三番五次 ...
- 让thinkphp 支持ftp上传到远程,ftp删除
让thinkphp真正的支持FTP上传.删除. 原创by default7#zbphp.com,如需转载请保留作者信息. /** * This is not a free software, All ...
- Linux递归解压缩一个目录下的全部文件
gunzip -r hongchangfirst/data 怎样递归删除那些剩余的非log结尾的文件? 先列出确认一下: find hongchangfirst/data -type f ! -nam ...
- 页面日期选择控件--jquery ui datepicker 插件
日期选择插件Datepicker是一个配置灵活的插件,我们可以自定义其展示方式,包括日期格式.语言.限制选择日期范围.添加相关按钮以及其它导航等.官方地址:http://docs.jquery.com ...
- tp表单的提交与验证
一.控制器如下 引用use app\index\model\User; //注意模型类名不能和控制器类名相同 public function index(){ return $this->fet ...