Makefile 8——使用依赖关系文件
Makefile中存在一个include指令,它的作用如同C语言中的#include预处理指令。在Makefile中,可以通过include指令将自动生成的依赖关系文件包含进来,从而使得依赖关系文件中的内容成为Makefile的一部分。
在此之前,先介绍一下Makefile中的include的用法。
.PHONY:all clean
DIR_DEP=dep
DEPS=test_deps
all: exe include $(DEPS) dep:
mkdir dep
exe:
@echo "exe" test_deps:$(DIR_DEP)
@echo "deps"
指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。 Makefile 中指示符
“ 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项目:
.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: $(EXE) include $(DEPS) $(DIRS):
$(MKDIR) $@
$(EXE):$(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o,$^)
$(DIR_OBJS)/%.o:$(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c,$^)
$(DIR_DEPS)/%.dep:$(DIR_DEPS) %.c
@echo "Creating $@ ..."
@set -e;\
$(RM) $(RMFLAGS) $@.tmp;\
$(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\
$(RM) $(RMFLAGS) $@.tmp
clean:
$(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如下所示:
.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)) ifeq ("$(wildcard $(DIR_OBJS))","")
DEP_DIR_OBJS :=$(DIR_OBJS)
endif#dir_objs
ifeq ("$(wildcard $(DIR_EXES))","")
DEP_DIR_EXES :=$(DIR_EXES)
endif#dir_exes
ifeq ("$(wildcard $(DIR_DEPS))","")
DEP_DIR_DEPS :=$(DIR_DEPS)
endif#dir_deps all: $(EXE) include $(DEPS) $(DIRS):
$(MKDIR) $@
$(EXE):$(DEP_DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o,$^)
$(DIR_OBJS)/%.o:$(DEP_DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c,$^)
$(DIR_DEPS)/%.dep:$(DEP_DIR_DEPS) %.c
@echo "Creating $@ ..."
@set -e;\
$(RM) $(RMFLAGS) $@.tmp;\
$(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
sed 's,\(.*\)\.o[:]*,objs/\1.o:,g' <$@.tmp >$@;\
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS)
这样就不会无限循环了,同样,如果不想make报那个警告没有什么那个文件或目录,在Makefile中的include加上符号 ’-‘,这个提示信息在这里是安全的,因为make就是这样设计的include指令。 改动主要是增加了三个变量,这三个变量的值根据相应的目录是否存在而分别赋值。如果不存在,就将目录名赋值给它,如果存在,则这三个变量的值为空,在Makefile中,就算没有定义一个变量,直接$(变量),此时变量为空,增加的三个变量都作为对应规则中的第一个先决条件,这样无限循环问题得到了解决。
但是,我很纳闷,为什么加了条件语法,这个死循环的问题就解决了?就算加了条件语法,生成在dep目录下的文件依旧会在我的文件系统上改变时间戳,那样还是会一直循环啊,为什么这里却得到了解决?这就要到最前面的Makefile中说起了,时间戳改变,make会去重新构建时间戳改变了的所以依赖内容,有了条件语句之后,就算时间戳改变了,但是条件语句会生成对应地条件来阻止无限循环,虽然条件语句没有放在类似C语言的while(1)这种无限循环的语句块中,但是时间戳的改变,会让make重新构建和时间戳改变文件的所以依赖,这样条件语句相当于是一直在while(1)中一样。最后说明的是,条件语句很像C语言中的条件编译,它应该在构建目标之前预编译,所以通常条件语法应该位于目标之前,如果放在目标执行完毕之后,条件语句将失去作用,毕竟条件语句是要去控制目标和依赖项的,位于它们之前也是理所当然的,Makefile中语句的大体运行顺序在上面有给出。
ifeq ($(wildcard $(DIR_OBJS)),)
DEP_DIR_OBJS :=$(DIR_OBJS)
endif#dir_objs
ifeq ($(wildcard $(DIR_EXES)),)
DEP_DIR_EXES :=$(DIR_EXES)
endif#dir_exes
ifeq ($(wildcard $(DIR_DEPS)),)
DEP_DIR_DEPS :=$(DIR_DEPS)
endif#dir_deps
也可以用上面的代替之前Makefile中的三个变量部分,之前用的空“”,括号中还加了引号“,其实按照GNU_MAKE上的示例,用的ifeq(XX,)表示如果XX为空就...
Makefile 8——使用依赖关系文件的更多相关文章
- Makefile 9——为依赖关系文件建立依赖关系
现在我们再对complicated项目做一些更改,增加程序文件间依赖关系的复杂度. /× main.c ×/ #include"foo.h" int main(void) { fo ...
- makefile自动生成依赖关系
手工编写依赖关系不仅工作量大而且极易出现遗漏,更新也很难及时,修改源或头文件后makefile可能忘记修改.为了解决这个问题,可以用gcc的-M选项自动生成目标文件和源文件的依赖关系.-M选项会把包含 ...
- Makefile 7——自动生成依赖关系 三颗星
后面会介绍gcc获得源文件依赖的方法,gcc这个功能就是为make而存在的.我们采用gcc的-MM选项结合sed命令.使用sed进行替换的目的是为了在目标名前加上“objs/”前缀.gcc的-E选项, ...
- 90%的人都不知道的Node.js 依赖关系管理(上)
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/nodejs-dependency-mana ...
- Makefile中头文件在依赖关系中作用
摘于:http://bbs.csdn.net/topics/120024677 (1)在makefile的依赖关系中用不用体现.h头文件?(2)如果在依赖关系中要体现.h头文件,应该体现到什么层次?= ...
- makefile 自动处理头文件的依赖关系 (zz)
现在我们的Makefile写成这样: all: main main: main.o stack.o maze.ogcc $^ -o $@ main.o: main.h stack.h maze.hst ...
- Linux Makefile 生成 *.d 依赖文件及 gcc -M -MF -MP 等相关选项说明【转】
转自:https://blog.csdn.net/qq1452008/article/details/50855810 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...
- Makefile依赖关系中的竖线“|”
网上搜索无果,于是自己查看了一下makefile的info文件,其中解释如下: [java] view plain copy print? target : prerequisites [TAB] ...
- Makefile中自动生成头文件依赖
为什么需要自动生成头文件依赖? 编译单个源文件时,需要获取文件中包含的头文件的信息,但是一般的Makefile不会在规则中明确写明文件依赖的头文件,所以单独修改头文件后,不会导致包含头文件的源文件重新 ...
随机推荐
- kali开启ssh服务,实现win远程登录
本人问题:想通过windows7中的putty直接ssh到kali系统,而默认情况下,kali系统ssh服务没有开启. 具体按如下操作进行设置: 照以下步骤进行配置和操作: 1.修改sshd_conf ...
- javascript实现浏览器窗口传递参数
a.html <html> <head> <title>主页面</title> <script language="javascript ...
- hdu 1024 dp滚动数组
#include <cstdio> #include <iostream> #include <algorithm> #include <queue> ...
- 使用mocha测试
学习了MOCHA官网的示例,将学习成果记录一下.[原文+例子:使用mocha测试] mocha是什么 Mocha是一个跑在node和浏览器上的javascript测试框架,让异步测试变得简单有趣, 并 ...
- Android选择/拍照 剪裁 base64/16进制/byte上传图片+PHP接收图片
转载请注明出处:http://blog.csdn.net/iwanghang/article/details/65633129认为博文实用,请点赞,请评论,请关注.谢谢! ~ 老规矩,先上GIF动态图 ...
- android gallery 自定义边框+幻灯片效果
最近在项目中用到图片轮播,试了Gallery,ViewFlipper,ViewPager,感觉Gallery最符合需求,但是Gallery的系统边框很难看,项目中要求用自己的背景图片. 下面来看一下使 ...
- HDU 1565 1569 方格取数(最大点权独立集)
HDU 1565 1569 方格取数(最大点权独立集) 题目链接 题意:中文题 思路:最大点权独立集 = 总权值 - 最小割 = 总权值 - 最大流 那么原图周围不能连边,那么就能够分成黑白棋盘.源点 ...
- TP 查询语句中如何使用 FIND_IN_SET 这样的查询方法
TP 查询语句中如何使用 FIND_IN_SET 这样的查询方法 $condition['_string'] = 'FIND_IN_SET('.$citys.',city)';
- vue + 百度地图api
主要分解为如下步骤: (1)在html文件中引入百度地图, <script type="text/javascript" src="http://api.map.b ...
- SQL Server 性能调优(方法论)【转】
目录 确定思路 wait event的基本troubleshooting 虚拟文件信息(virtual file Statistics) 性能指标 执行计划缓冲的使用 总结 性能调优很难有一个固定的理 ...