Makefile中指定了针对某个target的规则(rule). 注意Makefile中可以有多条规则: 即针对Makefile中的一条targets, prerequisites列表, 就有一条对应的规则.

规则指定了target: 要更新的目标, 及目标所依赖的先决条件, 并指定了更新目标所采取的行为: command.

如 果prerequisites比target更新, 那么Make就利用Makefile中所指定的命令脚本来更新目标. 同样地, prerequisites也可能是另一条规则中的目标, Make会查看属于它的先决条件是否比它更新以执行它的规则中的命令脚本. 这类似于C中的函数调用, 在Make中, 这些依赖关系建立了"依赖图"(dependency graph).

关于target和prerequisite的依赖性, 我们可以理解它有两方面的含义:
(1) 要生成targets, 先要保证它依赖的prerequisites是最新的
(2) 保证prerequisites在在targets之前被更新.

规 则包含两个部分, 一个是依赖关系, 一个是生成目标的方法. GNU Make支持五种规则: Explicit rules, Pattern rules, Implicit rules, Static pattern rules, Suffix rules. 后面会介绍它们. 在了解这些规则之前, 我们先弄清makefile中通配符, 伪目标, 变量, VPATH, vpath的用法.

通配符


在Makefile中可以使用通配符, GNU Make能够识别Bash的通配符(参考《学习Bash》, p22):
~       : 当前用户的home目录 
~zp    : 用户zp的home目录 
*.o      : 所有的目标文件 
?         : 单个字符 
[abc]   : a, 或b, 或c 
[a-zA-Z]: 所有大小写字母 
[!0-9]  : 所有的非数字

最好不要使用*通配符, 它可能使你无意引入一些不相关的文件.

出现在targets, prerequisites中的通配符由Make展开, 而出现在commands中的通配符由shell展开.

若想使用通配符所代表字符的原意, 在它之前加上转义字符/: 比如/?表示问号, 而非任意单个字符.

伪目标 (phony targets) 


正如前面所说, 目标不仅可以是文件, 也可以标签. 用作标签的目标就是一个伪目标. 伪目标一般用来定义一个代表一条命令脚本的标签(也可以为伪目标指定prerequisites).

GNU Make是无法区别目标文件和伪文件的. 这样就存在一个问题, 如果你在Makefile里用到了一个标签, 如clean, 用以删除生成的中间目标文件:

clean: 
   rm -f *.o 

如果当前目录刚好有一个名为clean的文件, Make就会把这个你想用作伪目标的clean与clean文件关联起来, 当你运行 $ make clean 时, 由于clean不存在prerequisites, make 会给出这样的信息: 
make: `hello' is up to date.

如何解决呢? 这里就引入了.PHONY. 以下面的格式来生命一个伪target:

.PHONY : clean 
clean: 
    rm -f *.o 

.PHONY告诉了Make: 它后面的prerequisite, 也就是clean是一个伪目标.

.PHONY的作用:
(1)告知Make, 该伪目标并不是真正的文件, 不遵循由源代码生成目标文件的规则.
(2)告诉Make, 该伪目标始终需要被更新.

由于伪目标始终需要被更新, 那么如果它作为某个目标文件的prerequisite, 那么在每次调用make时都会执行命令. 所以通常不将伪目标作为prerequisite. 但给伪目标指定prerequisite非常有用(实际上就是让这些prerequisites对应的命令在伪目标被指定为target的时候被执行).

我们来看看这个例子: 该makefile同时生成多个可执行文件, 而且只需输入"make"

all : prog1 prog2 prog3 
.PHONY : all

prog1 : prog1.o utils.o 
    gcc -o prog1 prog1.o utils.o 
prog2 : prog2.o 
    gcc -o prog2 prog2.o 
prog3 : prog3.o sort.o utils.o 
    gcc -o prog3 prog3.o sort.o utils.o

all是第一个target, 即为默认的target.  由于指定了all是一个伪目标, 所以all总是要被更新. 于是, 它的三个prerequisites: prog1, prog2, prog3所对应的规则也需要被裁决, 看它们各自是否需要被更新. 由此, 一条简单的make语句就可以生成多个可执行target.

注意: all : prog1 prog2 prog3 和 .PHONY : all 出现的前后顺序在这里是无关, 但最好将.PHONY : all放在前面, 这貌似更符合习惯.

注意, 在上述情况下, 如果生成的prog1, prog2, prog3不需要更新, make反馈的信息为:
make: Nothing to be done for `all'. 它不同于(非伪)目标不需要更新的信息: `<target>' is up to date.

变量


变量以这样的形式被定义:

$(variable-name)

它表示我们希望将名为variable-name的变量在makefile中扩展.(可以把它理解成C语言中的宏)

变量能包含所有的字符, 符号. 比如:$( compile.c)

变量的格式: 除了单个字符可以直接跟在$之后外( $?), 其他的变量都必须用()或者{}引起来.

我们可以自己定义变量, 同时, Make也会自动建立一些变量(自动变量).

自动变量(automatic variables)
自动变量一般用来表征target或prerequisites, 这样你就不用显式地指定它们的名称. 一共有7种自动变量:

$@    : target 
$%    : archive menmber specification 
$<    : 第一个prerequisite 
$?     : 所有比target新的prerequisites, 它们之间以空格分隔. 
$^    : 所有的prerequisites(但不包括重复的prerequisites). 
$+    : 与$?相同, 但包括重复的prerequisites. 
$*     : 目标文件名的主干(既不包括后缀), 它一般只用于pattern rules.

针对上面的自动变量, 有的版本的Make还提供了变种: 在变量名之后加上D表示变量的目录部分, 加上F表示变量的文件部分. 如: $(<D). 在GNU Make中提供了更好的表示目录和文件的方法, 所以不提倡用这些变种.

由于自动变量是Make在建立target和prerequisites的对应关系后自动生成的, 所以只能在command中用到它们!

利用VPATH和vpath来查找文件


对 于非常简单的项目, 我们可以把所有的源代码, 头文件和Makefile一起放在同一个目录. 但对于比较复杂的情况(有比较多的头文件, 源代码文件. 这往往是现实中经常存在的情况), 我们一般把代码分类, 放在不同的目录中. 一种比较简单的做法就是将源代码文件放在src(或source)目录, 将头文件放在include目录. 然后将Makefile放在它们的父目录.

如 果你不在调用make时明确地作出规定, make只在当前目录中搜索targets和prerequisites. make将makefile所在的目录默认为搜索路径优先级最高的目录, 即先在当前目录查找文件, 若找不到, 再到VPATH, vpath指定的目录查找.

下面以2种情况来说明文件查找的问题, 给出了对应的3个Makefile和2个.c文件.

VAPTH

VPATH语法: VPATH = dir1 dir2 ...

在UNIX下, 目录用" " 或 ":"分隔, 在windows下, 目录用 " "或";"分隔. 推荐使用空格!

1, 在当前目录创建一个src目录, 里面放置hello.0.c, 一个Makefile.0. 它们的内容分别如下:

/* hello.0.c  */ 
#include <stdio.h> 
int main(void) 

    printf("hello, world!/n"); 
    return 0; 
}

#Makefile.0 
hello: hello.c 
    gcc -Wall -o hello hello.c

在Makefile所在的目录运行make -f Makefile.0, 会得到这样的报错信息:

make: *** No rule to make target `hello.0.c', needed by `hello.0'.  Stop.

这正是由于Make默认地在当前目录搜索target和prerequisites. 将Makefile改写为如Makefile.1所示, 在其中引入了VPATH变量.

#Makefile.1 
VPATH  = src

hello.0 : hello.0.c 
    gcc -Wall -o hello.0 hello.0.c

运行 $ make -f Makefile.1, 我靠, 还是报错? 没办法, 把命令改为如下:
gcc -Wall -o $@ $<

这下就成功了, 是不是要使用VPATH, 命令行就必须用make的自动变量代替文件名呢 ?

下面在Makefile所在的目录创建include目录, 在里面放个hello.h (内容很简单: #include <stdio.h>), 并将hello.c改写为:

/* hello.1.c */ 
#include "hello.h" 
int main(void) 

  printf("hello/n"); 
  return 0; 
}

Makefile当然要改写:

#Makefile.2 
VPATH  = src include 
CFLAGS = -I include

hello.1 : hello.1.c hello.h 
    gcc $(CFLAGS) -Wall -o $@ $<

有两点值得注意的地方:
(1) src, include都包括在VPATH中, 要告诉Make在哪里搜寻source, .c和.h文件的目录都要指定.
(2) VPATH只是告诉了Make在哪里寻找文件, 而gcc并不知道在哪里寻找hello.h (即VPATH只和Makefile中的target, prerequisites相关, 而不和command相关). 所以要声明一个CFLAGS变量. gcc $(CFLAGS) == gcc -I src/ .
还有一个值得注意的地方: 这里使用$<, 指代第hello.1.c. 如果用$^, 会把hello.h也包含进去, 而gcc的命令行中是不带.h文件的. 如果有多个.c文件, 使用变量表征它们的时候就要斟酌一下了.

使用VPATH有一个隐患: VPATH在每个目录中搜索所有的source文件. 如果一个相同的文件名出现在不同的目录中, VPATH将第一个搜索到的文件作为结果.

考 虑下面的情况: make搜索hello.1.c和hello.h文件, 它首先在当前目录找到了hello.1.c, 但找不到hello.h, 于是它到VPATH, vpath指定的src和include去查找, 在src目录它又发现了hello.1.c文件, 但它将之前在当前目录找到的的hello.1.c作为优先的prerequist, 随后它又在include目录找到了hello.h: 将原本位于src目录中的hello.1.c拷贝到Makefile.2所在的目录, 修改其printf()语句, 让其显示"from the wrong dir!", 再次执行make -f Makefile.2, 运行新生成的hello.1, 我们会发现是错误的hello.1.c被编译了.

vpath 
使用vpath较之VPATH更灵活. 它的基本语法为:

vpath <pattern> <directory-list>

其中<pattern>和<directory-list>都是可选的, 由此有三种使用vpath的方法:
1, vpath <pattern> <directory-list>
指定符合<pattern>模式的文件的搜索路径为<directory-list>.
2, vpath <pattern>
清除符合<pattern>模式的文件的搜索路径.
3, vpath
清除所有通过vpath所设置的文件搜索路径.

<pattern>中需要包含"%"字符, 它类似于扩展符中的"*", 表示匹配0或若干字符. 如: %.c 表示所有以.c结尾的C源文件.

可以利用vpath将Makefile.2改为如下:

"vpath %.c src" 和"vpath %.h include" 告诉Make在src目录搜索.c文件, 在include目录搜索.h文件.
要注意的是, 如果hello.1.c仍位于当前目录, make还是会将它作为首选, 而非vpath所指定的位于src目录中的hello.1.c!

VPATH is good for finding sources, not for finding targets.

还可以利用vpath来指定目标文件的路径, 后面再讨论.

注意使用VPATH和vpath时使用自动变量!

VPATH和vpath的比较 
1, VPATH是make的特殊变量, 而path是make的关键字.
2, make首先在当前目录搜索文件, 并将位于当卡那目录的文件作为首选的prerequisites, 若在当前目录找不到该文件, 再到由VPATH或vpath指定的目录去搜索.

(一) 显式规则(Explicit Rules)


在makefile中显式指定了targets, prerequisites. 这即使用了make的显式规则, 它是我们常用的规则.

(二) 模式规则(Pattern Rules)


如果使用的文件比较多(比如需要使用大量的.o), 再使用显式规则, 输入那些.c, .h文件名就有点让人抓狂了. 在这种情况下可利用Make的自动推导(模式规则), 这样就不用针对每个.o文件声明command了.

利用下面的Makefile可以生成前面的hello.1:

#Makefile.4 
vpath %.c src 
vpath %.h include 
CFLAGS = -I include

%: %.c %.h 
    $gcc -Wall $(CFLAGS) -o $@ $< 
hello.1:

利用Pattern Rules, 可以让Make进行自动推导: Make看到一个finename目标, 就会将filename.c和filename.h加入到prerequisites.

Static Pattern Rules


它和前面的Pattern Rules只有一个区别: 限定了针对那些target使用你给定的命令. 可以将Makefile.4改写为:

#Makefile.5
vpath %.c src
vpath %.h include
CFLAGS = -I include

OBJECTS = hello1

%(OBJECTS): %.c %.h
    $gcc -Wall $(CFLAGS) -o $@ $<
hello.1:

学习GNU Make (2): 规则相关推荐

  1. 学习GNU Make (1)(转)

    在GNU/Linux环境下, 使用make编译程序是个不错的选择. 较之IDE, make更灵活, 更健壮, 尤其对平台适应力强.  实际上, make在上实际七十年代就出现了, 它的年纪可比你我大许 ...

  2. 《Python自然语言处理-雅兰·萨纳卡(Jalaj Thanaki)》学习笔记:07 规则式自然语言处理系统

    07 规则式自然语言处理系统 7.1 规则式系统 7.2 规则式系统的目的 7.2.1 为何需要规则式系统 7.2.2 使用规则式系统的应用 7.2.3 练习 7.2.4 开发规则式系统需要的资源 7 ...

  3. 学习GNU Emacs命令速查表(三)

    博客地址:http://blog.csdn.net/shuxiao9058 原始作者:季亚 第八章简章的文字排版和特效编辑 表8-1:制表位命令速查表 键盘操作 命令名称 动作 (无) edit-ta ...

  4. linux arm 汇编学习,如何在LINUX平台上学习GNU ARM汇编

    本例说明如何在LINUX平台上学习GNU ARM汇编. 1.软件环境 vmware 5.0 + redhat 9.0 + skyeye 1.2.4 + arm-elf 工具 2. 源文件 a.s -- ...

  5. 正则匹配常用匹配公式,运用举例,不用学习复杂的匹配规则,拿来就能用

    正则匹配千千万,常用匹配占一半.这里本实习生将常用的正则匹配整理如下,不用学习复杂的匹配规则,拿来就能用的正则匹配,才是好的正则匹配! 限定符:(匹配出现的次数) * >=0次 + >0次 ...

  6. WCF学习笔记(基于REST规则方式)

    一.WCF的定义 WCF是.NET 3.0后开始引入的新技术,意为基于windows平台的通讯服务. 首先在学习WCF之前,我们也知道他其实是加强版的一个面向服务(SOA)的框架技术. 如果熟悉Web ...

  7. linux 路由指向策略,Linux 路由 学习笔记 之六 策略规则的添加

    上一节分析了策略规则相关的数据结构,本节就分析一下策略规则的添加.对于策略规则的功能模块,由于 v4 . v6 都有用到,所以该模块也和邻居模块一样,抽象出了通用规则的接口函数,然后根据传入的参数来进 ...

  8. 论文浅尝 | 基于表示学习的大规模知识库规则挖掘

    链接:www.ict.griffith.edu.au/zhe/pub/OmranWW18.pdf 动机 传统的规则挖掘算法因计算量过大等原因无法应用在大规模KG上.为了解决这个问题,本文提出了一种新的 ...

  9. HFM深入技术学习系列之二--规则

    学习的路线 学习如何写规则,从技术的角度看,从以下几点入手: 0 HFM合并报表的基本业务功能 1 规则的入口,即HFM是从哪里调用我们写的规则. 2 规则的基本语法,规则是用VBSCRIPT写的,这 ...

最新文章

  1. jQuery的jquery-1.10.2.min.map触发404(未找到)
  2. mysql count() 写法,MySQL COUNT()用法及代碼示例
  3. except but
  4. make后gcc出现不全_Linux零基础:C语言和gcc
  5. android 拨打紧急号码,通话时开启免提功能实现
  6. apache 伪静态 (转)
  7. 【移动端 Web】怎么循序渐进地开发一个移动端页面
  8. oj系统格式错误_论文查重会不会检查格式?【paperpp吧】
  9. MyBatis系列-Mybatis入门精讲
  10. OSTaskSuspend()——挂起任务
  11. Java中GUI中菜单栏
  12. GET和POST请求是一个字典
  13. mysql replication 延时_MySQL:延迟的主从复制 ( Delayed Replication )
  14. rsa加解密及加签验签
  15. Python排序算法---冒泡排序
  16. 2016版excel_想要下班直接打卡,这些Excel技巧可以帮到你
  17. 页面在微信端禁止缩放
  18. 上古卷轴5:重制版窗口化运行1920*1080画面显示不全的问题解决
  19. 【C语言】#文件操作#有5个学生,每个学生有3门课程的成绩,从键盘输入以上数据(包括学号、姓名、3门课成绩),计算出平均成绩,将原有数据和计算出的平均分数存放在磁盘文件stud中。
  20. 参考C++高级进阶教程

热门文章

  1. 15.try...except...finally
  2. 第一次作业:深入源码分析进程模型
  3. mk-parallel-dump 实验
  4. 嘿!你的“苹果”已经被盯上啦
  5. 串口速度,RS232与MAX232的区别
  6. 有关Silverlight TreeView组件的研究[3]——Silverlight学习笔记(8)
  7. 两个整数集合的交集 ———— 腾讯2014软件开发笔试题目
  8. case when then的用法-leetcode交换工资
  9. CompletableFuture 详解
  10. CTSC2017酱油记