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"

 好好分析上图的运行结果,能让自己更好地理解后面的东西。
 
include”指示符告诉 make 暂停读取当前的 Makefile,而转去读取“ include”
指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。 Makefile 中指示符
“ include”书写在独立的一行 。
通常指示符“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——使用依赖关系文件的更多相关文章

  1. Makefile 9——为依赖关系文件建立依赖关系

    现在我们再对complicated项目做一些更改,增加程序文件间依赖关系的复杂度. /× main.c ×/ #include"foo.h" int main(void) { fo ...

  2. makefile自动生成依赖关系

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

  3. Makefile 7——自动生成依赖关系 三颗星

    后面会介绍gcc获得源文件依赖的方法,gcc这个功能就是为make而存在的.我们采用gcc的-MM选项结合sed命令.使用sed进行替换的目的是为了在目标名前加上“objs/”前缀.gcc的-E选项, ...

  4. 90%的人都不知道的Node.js 依赖关系管理(上)

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/nodejs-dependency-mana ...

  5. Makefile中头文件在依赖关系中作用

    摘于:http://bbs.csdn.net/topics/120024677 (1)在makefile的依赖关系中用不用体现.h头文件?(2)如果在依赖关系中要体现.h头文件,应该体现到什么层次?= ...

  6. makefile 自动处理头文件的依赖关系 (zz)

    现在我们的Makefile写成这样: all: main main: main.o stack.o maze.ogcc $^ -o $@ main.o: main.h stack.h maze.hst ...

  7. Linux Makefile 生成 *.d 依赖文件及 gcc -M -MF -MP 等相关选项说明【转】

    转自:https://blog.csdn.net/qq1452008/article/details/50855810 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...

  8. Makefile依赖关系中的竖线“|”

    网上搜索无果,于是自己查看了一下makefile的info文件,其中解释如下: [java] view plain copy print? target : prerequisites   [TAB] ...

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

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

随机推荐

  1. 彻底解决Odoo8.0单时区应用中的时区问题

    原文地址:http://shine-it.net/index.php/topic,17001.0.html 由于数据库中存储的是UTC时区,默认情况下数据导出和group by都存在时区问题.彻底解决 ...

  2. 安装Office2007时出现1706错误的解决方案

    前几天,重做了系统.周末因为接到一笔单子,很兴奋啊.第一次接到私活.然后就装Office2007,打算看需求的.居然安装的时候出现错误,提示1706错误,后面一串错误信息,也懒得看,以为是文件坏了. ...

  3. ubuntu14.04如何卸载mysql

    1. 删除mysql的数据文件 sudo rm /var/lib/mysql/ -R 2. 删除mqsql的配置文件 sudo rm /etc/mysql/ -R 3. 自动卸载mysql的程序 su ...

  4. Maven核心概念之仓库,生命周期与插件

    宏观图 一.仓库 统一存储全部Maven项目共享的构建的位置就是仓库. 仓库分为本地仓库和远程仓库.远程仓库又分为中央仓库(中央仓库是Maven核心自带的远程仓库),伺服(还有一种特殊的远程仓库,为节 ...

  5. [Compose] 19. Leapfrogging types with Traversable

    We use the traversable instance on List to reimplement Promise.all() type functionality. For example ...

  6. octopress github 换电脑 使用

    octopress github 换电脑 使用

  7. Bash Shell的环境配置文件

    login shell:取得bash时需要完整的登录流程 non-login shell:取得bash接口的方法不需要重复登录,举例来说,你以x window登录Linux后,再以x的图形界面启动终端 ...

  8. Android中如何判断升级用户

    借助PackageInfo 转自:http://blog.saymagic.cn/2016/05/31/howto-judge-update-user.html 由于上面两种自定义的逻辑都不能很好的满 ...

  9. 02-maven常用命令,以及使用命令创建目录

    maven常用命令 mvn  -v 查看maven版本 compile 编译 test 测试 package 打包 clean 删除target install  安装jar包到本地仓库中. mave ...

  10. Android Native IPC 方案支持情况

    Binder - 不支持Native层的binder 内存共享 - 不支持 信号量(信号灯) - 不支持 消息队列 - 不支持 信号 - 支持,但是不能用sigqueue传消息,只能用来安装信号,可以 ...