怎么写Makefile
内容简要说明
本文主要说明怎样写一个 makefile,用于 UNIX/C 编程环境下。对于每一
方面,我将只说明最简单的用法(别的我也不会,呵呵)。同时,因为我没
有办法在很多系统上测试 makefile ,所以只能说明一些应该是通用的参数
或用法。其他更深入的内容请参考手册:man make 。
呵呵,本文预先假设你知道 unix, c, make, cc, ar 等等是干什么用的。
这样我就不用解释过多东西了。
注: <s> 是指一个空格或一个tab - 制表符
<tab> 专指一个制表符!
make 的工作原理和方法简介
make 根据一个或多个文件定义的一系列规则,通过运行其他程序如:cc,
ar 等等来建立调用者想得到的目标,它预先检查依赖性以至于不必作一些
无用功。makefile 是指描述这些目标、规则以及依赖性的一个文件。
如果 make 认为一个目标需要重新建立,即不是最新的,它就根据所知道
的规则来一步步建立这个文件,中间也建立这个目标的依赖者 - 如果需要
的话。如果 make 认为某个目标不需要重新建立,即不是最新的,那么它就
什么都不做。有的系统会说 target is up to date.
这样 make 先搜索到一个目标(比如说 all ),然后把它的依赖者当作目
标继续搜索,直到 makefile 中再没有对目标的描述了。每个目标除非
1,目录下面有一个同名的文件,或者
2,有目标下有一个描述(不一定非要能够或可以产生这个文件),或者
3, make 自己知道怎么产生这个文件;
否则 make 会说 Don 't know how to make target 'target '
注意 make 只会对搜索的第一个目标采取动作,即搜索与之相关的其他依
赖者以及建立的方法并运行命令等,此后即使你写了一个完整的目标描述,
如果没有把它与上面的第一个目标相关联的话(关联可以是间接的,即通
过好多中间目标),make 将不会为你检查该目标的依赖性,以及运行命令
并且建立这个(些)目标!!这一点比较悲惨是吧?避免让 make 忘记 建
立目标的最好的方法就是在开头先写一个 all: alltargetslist!^_^
参见例子。
make 的命令行开关
-f filenamelist 指定 make 用的 makefiles,可以是一个或几个。如果
没有 -f,make 搜索的顺序是 makefile, Makefile, ...... 所以本文中
一律称为 makefile
-i 忽略 make 执行命令时检测到的错误,继续执行其他的命令。相当于
在 makefile 中有特殊功能:.IGNORE
-n 只读取 makefile 文件并建立规则,但不执行相应的命令,这样可以
把命令打印出来供参考。注意有些特殊定义的命令将被执行,比如 .POSIX
mode 中,前面带 + 的命令。
-p 打印出 make 搜索到的完整的宏定义和目标说明。这个开关往往带有
很多输出,因为 make 要搜索的文件很多。
-s Silent Mode. 这样 make 将在执行前不显示要执行的命令。相当于
在 makefile 中有特殊功能:.SILENT
其他开关参见手册。
makefile 的宏定义
基本语法是 macro=value
如果 value 一行写不完可以用 <s> \ 续行。
引用的时候要加 $ 和括号,如 $(macro),或 ${macro}
注意:
1、宏名一般用大写以引起注意。
2、value 可以是以空格分开的好多个(比如目标,或者。。。)
3、你可以把一下所说的目标和依赖者预先用宏定义一下,这样以后就可以少
写点东西,参见例子。
makefile 的目标写法
基本语法:
头一行是 target1 target2 ...: denpendency1 denpendency2 ...
注意到目标可以是列表,这样就跟一个一个目标的写法一个意思。但我一般
一次只写一个目标,因为好像多个目标在一块儿的时候下面的 dynamic
macros 没法用(值不是我想要的)。这样就是:
target: denpendency1 ...
冒号后面是此目标的依赖者,也就是说,此目标的建立将依赖于后面列表中
的每个程序- 通常也是 make 的一个目标。
如果依赖者一行写不完可以用 <s> \ 续行。
然后就是对建立这个目标的描述:即怎样建立这个目标 - 一系列命令。
当然也可以什么都不写。这样 make 就不会建立任何目标( all 通常就是
这样)。但 make 检查依赖性。下面所说的 all 也就是通过这个原理来建
立冒号之后的其他目标:)
** 注意:每行命令必须以一个tab为开头!!!后面跟要运行的命令,如
cc ......如果一行写不完可以用 <s> \ 续行。续行也必须首先以一个tab
为开头!!!
命令前面(必须在tab后面)可以有 +, -, @, ! 等特殊符号,各有特别的
意义,这个我反正极少用,只好参考手册了:(
注意好多 makefile 中都一个目标叫 all,我原先以为是 make 的一个默认
目标,其实不是,make 没有任何默认的目标。不过在一开始写一个 all 的
目标是一个好习惯,因为这样编辑 makefile 时一下就能发现 make 会检查
哪些目标。这种情况我一般称 all 是 虚目标,而冒号之后 all 的依赖者称
为 实目标,即实际上想让 make 生成的目标。但是,如果该目录下面同时有
一个名为 all 的文件,那样就会引起 make 的混乱;如果真是这样,那你可
以改用 alltargets 什么的。
makefile 中的特殊东西
注释和 include
注释:任何前面有 # 的行。
注意如果一个目标的命令后有 # , make 会照常把它传递给 shell,然后
shell 也当作是注释:)
如果开头有 include filename 等行,make 会先 include 这个文件,就象
C 语言中的 include 差不多。
Special_Function Target
在 makefile 的开头写 .IGNORE .SILENT .PRECIOUS 等等是什么意思?
.IGNORE 和 .SILENT : 见上面 make 的命令行开关部分。
.PRECIOUS 是这样的:当 make 正在进行 make 的时候,如果你按下
Ctrl-C,make 就会把当前这个目标文件给删除掉。如果 makefile 开头
有这个 .PRECIOUS,make 就不会这样做。
还有 .SUFFIXES 也很有用,主要是用来定义程序后缀的。不过对 C 语言
的用户来说,无非是 .c,.o 两种,都是应该已经定义好的,就不需要了。
Special_Rules Target
定义了 .SUFFIES 以后,你就可以为一些程序编译工作定义一些通用的规
则,例如在 C 语言中一般 .c 编译到 .o 文件的过程和参数都差不多,都是
cc -c -g ***.c 什么的。这样你可以在 makefile 中这样写:
.c.o:
<tab> cc -c $ <
这样 make 在建立目标的过程中,需要把 .c 变成(一般是编译到).o 文件
的时候就运行 cc -c ***.c 产生 .o 文件。这样你就不必为每一个 .c 到
.o 定义规则了。
注:我发现好像这个 $ < 只有在这里才起作用,不知为什么?
注意这个规则不必也没法写在另一个目标里面作依赖者,所以这个规则的依赖
性已经预先定义好了:.o 文件总是依赖于 .c 文件,除非你另外定义。
Dynamic Macros
也只能解释其中几个我经常用的。
1、$* 我只能以例子说明。比如你写:
target1.c: ...
<tab> command $* ....
这个时候的 $* 是指 target1 。这样 $* 可以用来处理编译或预编译过程
中产生的中间文件,如 .o 等等(就写成 $*.o)
2、$ < 见上面的特殊规则说明。
3、$@ 指当前正在建立的目标名。如上面的写法中,$@ 是指 target1.c
这样 makefile 中的东西我知道的基本上解释完了,其实学写 makefile
的最好的方法是 copy 一个来改成自己所需要的,我一开始也是这么干的!
为了方便大家,我最后给几个简单的例子:)
注意调试的时候多用用 make -n, -f ... 什么的。
例子 1:
这个是最普通的。
all: program1 program2
program1.o: program1.c program1.h
cc -c -g -O program1.c
program2.o: program2.c program2.h
cc -c -g -O program2.c
program1: program1.o
cc -o $@ program1.o
program2: program2.o
cc -o $@ program2.o
如果要简化一下,可以这样写:
all: program1 program2
.c.o:
cc -c $ <
program1: program1.o
cc -o $@ program1.o
program2: program2.o
cc -o $@ program2.o
但这时,如果你修改了 program1.h or program2.h,make
不会知道要重新编译,因为没有定义对 .h 文件的依赖性。
例子 2:
这个例子中,该目录下面有3个文件:abc1.c abc2.ec abc3.c
我想利用 ar 建立一个静态库 libtemp.a,用于别的程序。
这个例子中,make 不会为你执行 clean 下面的命令。
除非你在 all 的 libtemp.a 后面加上一个 clean
esql 是数据库 informix 的预编译程序,它把 .ec 文件预编译
成 .c 文件,然后你可以用 cc 编译之,然后通常 .c 这个中间
文件就不需要了,要删除掉。
这里 .ec.o 的写法中就是用了这个 $* 。注意 rm $*.c 只会删除
由 .ec文件生成的 .c 文件,不会删除你自己的文件。
.SUFFIXES: .c .ec .o
# $HOME is already defined by your system!
INC=$(HOME)/include
BIN=$(HOME)/bin
LIB=$(HOME)/lib
CC=cc -I$(INC) -L$(LIB)
CFLAG=-c -g -O
EC=esql -I$(INC) -L$(LIB)
EFLAG=-c -g -O
all: libtemp.a
.c.o:
$(CC) $(CFLAG) $ <
.ec.o:
$(EC) $(EFLAG) $ <
rm $*.c
LIBTEMP=abc1.o abc2.o abc3.o
libtemp.a: $(LIBTEMP)
ar -r $@ $(LIBTEMP)
clean:
rm *.o
mv libtemp.a $(LIB)/libtemp.a