文章作者:linuxaid工程师sunmoon
Make是一种代码维护工具.在大中型项目中,他将根据程序各个模块的更新情况,自动的维护和生成目标.能很好的做到”不多”,”不重””不漏”.
不多:他只更新那些需要更新的文件,而不懂那些并不过时的东西
不重:是指当make对某个文件进行更新时,即使有很多文件过时,他也将只更新一次,当然,这一次就足够了
不漏:是指他不会漏下任何应该更新的文件.
1,make的基本使用
make在使用时有一系列的规则,他将根就这些规则来解释他的配置脚本,以达到它的设计目的.这个配置脚本的缺剩名是makefile ,Makefile.也可以用其它文件名.我们这里所讲的make的应用就是怎样写出高效,简洁,正确的makefile文件.
Make的使用形式为
Make [option] [macrodef][target]
Option指出make的工作行为,make的具体选项有:
-c dir make在开始运行后的工作目录为指定目录
-e 不允许在makefile中队环境的宏赋值
-f filename 使用指定的文件作makefiel
-k 执行命令出错时,放弃改目标去维护其它目标
-n 按实际运行时的执行顺序现实命令,包括用@开头的命令,但不振的执行
-p -现实makefile中所有的宏定义,河内部规则
-r 忽略内部规则
-s 执行但不显示命令,常用来检查makefile的正确性
-S 执行make时 出错即退出
-t 修改每个目标文件的创建日期,但又不正的创建
-V 打印make的版本号
-x 将所有的宏定义输出的shell环境中
-i 忽略运行make 中的错误,而又不退出make
在使用make 维护目标的时候,为了调试错误,常使用 make 2 > errofile.这样错误信息就都写入了errofile
2.1makefile 的基本书写
make 使用的依据是依赖关系.如又工程example ,他依赖于main.o e1.o e2.o,而main.o 依赖于main.c l.h .e1.o依赖于e1.c l.h ,e2.o 依赖于e2.c.
这样就形成一棵树.及双亲节点,依赖于孩子节点.当make 进行维护时.他呼对这棵树.进行一次后序遍历.如果发现孩子节点形成的时间晚于双亲节点形成的时间.便开始维护.原理十分简单.
现在,我们开始,我们的主要内容: 如何书写makefile.makefile 是供make 使用的一个配置脚本.他是按一定的规则写成的.它的组成是以一个要维护的目标为一个单元,每个单元的书写形式如下:
要维护的目标列表 要维护的目标属性 分割符(一般为 依赖的文件列表 命令行的属性 ;第一条命令
tab键 命令1…..
需要说明的是,第一条命令可以加; 跟在依赖文件列表后,也可以和其它的命令另起一行写.一定要记得加tab键.否则make 不认识.当一条命令太长一行放不下是使用/l来换行.
注释行用#开头.
这样一来.我们前面举的例子就可以写成这样的makfile
example : main.o e1.o e2.o
gcc -o example main.o e1.o e2.o
main.o :main.c l.h
gcc -c main.c
e1.o :e1.c l.h
gcc -c e1.c l.h
e2.o:e2.c
gcc -c e2.c
可以用cat -e -v-t makefile 其中-v-t可以让tab键显示^I ,是makefile 的末尾显示$,这样可以检验,makefile的正确性.
可见,书写一个简单的makefile文件并不困难.然而在大中型项目中.要通过makfile 做大量的维护与调试.这样会是程序员增加很大的工作量.因此,make 引入了一些属性变量,和宏变量,来控制make .甚至可以像c预言的条件编译那样.通过一些shell语句来控制.接下来.我们将一一讲解.
2.2 makfile中的属性控制
2.2.1分割符
分割符一般用: 但也用::,:!:^,:-一下举例说明一下这几个分割符的作用一般目标文件只能出现一次.只有使用::时才可以出现多次如:
a.o::a.c
#命令1
a.o::b.c
#命令2
意思是:如果a.c更新.使用命令1.b.c更新使用命令2
:^ 将指定的依赖文件和目标文件一有的依赖文件结合起来
:- 清除目标文件原有的依赖文件,将指定文件为目标文件的唯一依赖文件
:! 对每个更新过的依赖文件都执行一遍命令菜单
如:
a.o :b.c
a.o:^ c.c
此时a.o 的依赖文件是b.c 和c.c
a.o:- a.c
此时 a.o 的依赖文件只剩下a.c了
2.2.2 命令行属性
命令好属性较简单只有三种
+ 当运行条件成熟(依赖文件过时) 始终执行,即使 make 有 -n -q -t 选项
- 当此行出错时,忽略继续运行make
@ 不显示本行命令(make 执行使命令行在屏幕上打印)
2.2.3 目标文件属性
目标属性比较多.这里举几个常用的例子
.IGNORE 类似于命令行中的减号,在本目标维护出错时.并不马上退出make 而是继续运行
.SILENT 类似于命令行中的@ ,维护本目标时不在屏幕上显示
.PRECIOUS 保留中间文件
.LIBARY 说明维护的目标是一个库
.SYMBOL 说明维护的目标是指定入口的库成员
.LIBRARYM 说明维护的是库的一个成员
在使用目标文件的属性时有两种方法
.IGNORE main.o e1.o 和
main.o .IGNORE : ….
……
e1.o .INGORE :…..
…….
效果是相同的,在实际使用时,可以根据个人的具体情况使用.
2.2.3 伪目标
在make 中有两类目标,一类是实目标.一类是伪目标.伪目标不要求make生成世纪的目标文件,只是完成一些辅助性的工作,如打印,删除等.伪目标有两种.一种是任意给出名字如:
lp:
pr *.c/lp
将所有的源文件分页打印.由于系统中没有lp 文件,他总是认为是过时的,因此这个命令总被执行.
另一种方式是使用linux提供的伪目标
.ERRO :依赖文件
命令行
作用时,一旦遇到错误时,执行命令行
.INCLUDE :file1 file2
作用是: 将文件包含进来,注意:这里的包含不是指针的转移而是拷贝
.INCLUDE /usr
.INCLUDE file1
make 首先在当前目录下寻找file1,如没有则在/usr下找.另一种使用方法是:
/INCLDE <dir/filename>
.IMPORT 将一些宏载入 .如:
.IMPORT shell 这样makefile 便可以使用shell 里的宏了.
.EXPORT 和.IMPORT恰恰相反.是将makefile里定义的宏,输出到环境.不赘述.
当需要维护几个目标时可使用这样的伪目标:
all: main.o main1.o ….
重要说明是: 命令行属性>>目标文件属性>>make 命令行属性
3.宏的使用
我们在各种应用中已熟悉宏的概念.不赘述.在make 中使用宏也要先定以后使用.宏名可以是任意数字.字母 下划线的组合. 不过不能用数字开头.
宏的定义方式有3种
= 直接将后面的字符串赋给宏
:= 后面跟字符串常量,将他的值赋给宏
+= 宏原来的值加上一个空格,将它的内容赋给宏
宏的应用各是有两种:
$(宏名) 或 ${宏名}
宏名可以嵌套使用.如
h2=mylib.a
index=2
则在makefile 中调用$(h$(index)) ,就等于调用 mylib.a.使用shell 环境的宏.不需要定义.只要用.IMPORT 在如就行.
3.2 内定义宏
控制宏为了控制make 的属性,定义了一系列的内定义宏.分为两类.控制宏用于控制make 的属性.属性宏代表一些特殊的值.
控制宏:
.DIREPATR 表示路径中用于分割目录与文件的分割符一般为 /
.MAKDIR 调用make的绝对路径名
.NULL 空字符串
.PWD 运行make 时活动目录的绝对目录名
.SHELL 运行是的shell 名
另一类宏.我们成为属性宏.和目标文件的属性书写和意义都相同.用于设置这些属性.如
.IGNORE=yes
将整个makefile里的目标文件都设为.IGNORE.其它的属性宏.和目标文件属性里面的相同.
3.3 动态宏
引入动态宏的目的是,简化makefile的书写.他利用了很多一用的代码.但给makefile 带来了不可读性
$@ 当前目标文件的名字
$% 同$@,不过若维护的目标是库时.$@指库名 $% 指库成员
$> 适用于lib(member)的情况$>指库名 如$@
$* 目标文件,去掉后缀
$& 当前目标文件.在所有依赖文件
$^ 当前目标文件 在本单元中的依赖文件
$? 当情目标文件 在所有单元中比目标新的依赖文件
$< 当前目标文件 在本单元中比目标文件新的文件
这些宏只能用在命令行中.make 提供可与此对应的动态宏用在依赖文件中.他们分别是在上面的宏上在加一个$
$$@ 在依赖文件列表中,代表目标文件的名字
等等.这样,我们原先的makefile便可以写成:
example:main.o e1.o e2.o
gcc -c $?
main.c e1.c:$$*.c l.h
gcc -o $*.c
e2.c :$$*.c
gcc -o $*.c
3.4 宏的修改
一个宏定义以后.可以用make的某些功能.对其进行修改
1,如:file=/usr/l/main.c 引入修改描述符:
d :仅展开目录
b :仅展开文件名,不包括扩展名
f :展开全名
这样$(file:d)等于main . 其它的类推
2,替换宏中的字符串 规则 宏名: s /远字符串/新字符串
file =file1.o file2.o file3.o
file:s/file/fun
则file=fun1.o fun2.o fun3.o
3.还有 宏名:^前缀 宏名:+后缀
file= a b c
file=^"1"则
file=1a 1b 1c
4.内部规则
4.1make 的内部规则是系统或者后呼吁定义的一些规则.他在定义时.不必指出全名.只是定义了一些最常用的依赖关系和依赖命令.和c 编程有关的就以下.这几种情况
与c编程又过的文件扩张名是
.o 目标文件
.c c语言源文件
.C c++语言源文件
.h 头文件
与c 相关的规则主要有
.c:
$(CC)$CFLAGS)-O $@ $<
.c .o:
$(CC)$CFLAGS)-c $@ $<
这里面使用到了一些宏.以上两句话的意思是对.c 文件缺剩是的编译.和编译成.o 文件.有必要说一些宏的定义
宏名 初始值 说明
AR Ar 库管理命令
ARFLAGS -ruv 库管理命令选项
CC cc C编译命令
CGLAGS -o C编译命令选项
C++C CC C++编译命令
C++FLAGS -O C++编译命令
MAKE make MAKE命令
MAKEFLAGS NULL MAKE命令选项
LIBSUFFIX .a 库的扩展名
A .a 库的扩展名
有了内部规则.我们的makefile 会更加简练如原先的makefile 我们可以写成
example:main.o e1.o e2.o
gcc -o example main.o e1.o e2.o
main.o e1.o :l.h
4.2修改和定义内部规则
修改内部规则.可以有两种办法:一是修改内部规则用到的宏.而是直接修改内部规则的命令行
如我们前面提到的规则:
.c .o:
$(CC)$(CFLAGS)-c $@ $<
我们只要定义 CC=6788 其命令行就变成了 6788 -O -c $@ $< 原因是makefile 内部的宏定义大于make 的默认定义
至于写该命令行,直接在makefile 中写就行了.他会踢换掉默认的定义 如
.c .o:
$(CC) -c $@ $<
就会以下面这个定义为主
对于一些没有的后缀名可以自己定义,各时如:
new roman 后缀名1 后缀名2 …
.SUFFIXES .n 就定义了一个新的后缀n .从目标文件生成文字表文件的命令是:
nm 目标文件 > 名字表文件
现在我们定义一个内部规则,即.o 文件依赖于通明的.o 文件
.SUFFIXES .n
.o.n
nm $< > $@
这样的话.make 会根据.o文件的更新,自动的维护.n 文件
5.库的使用
5.1库的建立和维护
在开发大中型软件时.常把一些编译好的模块同意放到一个库中.在编译时可以节省时间和空间.
库中的文件一般成为库的成员.成员的表示形式一般为
库名(库成员名) 如: mylib(myrea.o) 用make 维护库时,我们将库名作为目标文件格式如下:
库名: 库名(库成员名1) 库名(库成员名2) 库名(库成员名3)
make 遇到这样的命令行 ,自动吧库名当成目标文件,并赋予.LIBRARY属性 例如:
mylib.a :mylib.a(myread.o) … 也可以这样定义
mylib.a .LIBRAAY :myread.o …还有一种定义方式是:
库名: ((rentry))
make 遇到这种情况,把库名置成.LIBRARY 把entery 置成 .SYMBOL.然后根据entry 找到相应的文件以后的处理和直接些文件相同
make 从.o 文件维护库的命令是
ae -ruv 库名 目标文件名
如维护一个名为mylib的库.makefile 需要这样写
mylib:mylib(f1.o)
gcc -c fi.c
ar -ruv muylib f1.o
rm -f f1.o
mylib:mylib(f2.o)
gcc -c f2.c
ar -ruv muylib f2.o
rm -f f2.o
……
利用动态宏我们可以将他该成这样:
mylib:mylib(f1.o f2.o…..)
gcc -c ($?:b).c
ar -ruv $@ $?
rm -f $@
我们也可以利用内部规则,来简化所有makefie 中 库的维护
.o.a:
$(AR) $(ARFLAGS) $@ $?
rm -f $?
然后库的makfile 写成
mylib: mylib(f1.o) mylib(f2.o)………
还有一条内部过则是:
.a
make $@
即make 将自动维护所有的库
5.2 利用库进行连接
使用库进行连接时.只需要将库名.o文件名说明就行.形式如下:
库名(库成员名) 或 库名(((成员entery))
例如想在有一可执行程序eampl2.exe 依赖于mylib 中的f1.o ,在makefile 中可以这么写:
eampl2.exe:mylib(f1.o)
#连接命令行(一般是ld)
make 读到这样的规则就知道
mylib 是一个库.有.LIBRAAY的属性
f1.0 是库的成员函数 有.LIBRAYM的属性
eampl2.exe 依赖于f1.o
在这里也有意跳内部规则
.o.e
$(LD) $(LDFLAGES) -o $@ $< $(LDLIB)
其中宏的定义是
LD 连接命令ld
LDFLAGEA ld 的选项]
LDLIB 库名列表
因此只要把你自己定义的库,加到库名列表中,在使用内部规则就行了.如上例可以写成:
LDLIB+=mylib.a # 将自定义的库加到 库名列表中去
eaple2.exe:mylib(f1.o)
7.条件编译
make 中还引入了一些shell 语法.以帮助进行类似于c 语言的于编译的功能.下面是在linux核心的makfile 中抄来的一段.我们可以看一下他是如何进行条件维护的
ifeq (.config,$(wildcard .config))
include .config
ifeq (.depend,$(wildcard .depend))
include .depend
do-it-all: Version vmlinux
else
CONFIGURATION = depend
do-it-all: depend
endif
else
CONFIGURATION = config
do-it-all: config
endif
类似于c 语言的 if else 语句.不同的是他是比较两个值是否相等,来判断是否满足条件.因为在makefile中.很多值是由字符串组成的,所以这个于就十分有用.
还有这样的语句:
ifdef CONFIG_PCI
DRIVERS := $(DRIVERS) drivers/pci/pci.a
endif
这个语句够简单,我多说什么了.
本文只是一个简单的说明.make 的内容很多.有兴趣的可以参考它的man 手册.不过编制好的makefile ,是要在实践中掌握的