1.Makefile 基本结构
Makefile 是 Make 读入的惟一配置文件,因此本节的内容实际就是讲述 Makefile 的编写规则。在一个 Makefile 中通常包含如下内容:
1)需要由 make 工具创建的目标体(target),通常是目标文件或可执行文件;
2)要创建的目标体所依赖的文件(dependency_file);
3)创建每个目标体时需要运行的命令(command)。
它的格式为:
target: dependency_files
command
注意:在 Makefile 中的每一个 command 前必须有“Tab”符,否则在运行 make 命令时会出错。
例如,有两个文件分别为 hello.c 和 hello.h,创建的目标体为 hello.o,执行的命令为 gcc。编译指令:gcc –c hello.c,那么,对应的 Makefile 就可以写为:
#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c –o hello.o
接着就可以使用 make了。使用make 的格式为:make target,这样 make 就会自动读入Makefile(也可以是首字母小写 makefile)并执行对应 target 的 command 语句,并会找到相应的依赖文件。如下所示:
[root@localhost makefile]# make hello.o
gcc –c hello.c –o hello.o
[root@localhost makefile]# ls
hello.c hello.h hello.o Makefile
可以看到,Makefile 执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。
2.Makefile 变量
上面示例的 Makefile 在实际中是几乎不存在的,因为它过于简单,仅包含两个文件和一个命令,在这种情况下完全不必要编写 Makefile 而只需在 Shell 中直接输入即可,在实际中使用的 Makefile 往往是包含很多的文件和命令的,这也是 Makefile 产生的原因。下面就可给出稍微复杂一些的 Makefile 进行讲解:
sunq:kang.o yul.o
Gcc kang.o bar.o -o myprog
kang.o : kang.c kang.h head.h
Gcc –Wall –O -g –c kang.c -o kang.o
yul.o : bar.c head.h
Gcc - Wall –O -g –c yul.c -o yul.o
在这个 Makefile 中有 3 个目标体(target),分别为sunq、kang.o和yul.o,其中第一个目标体的依赖文件就是后两个目标体。如果用户使用命令“make sunq”,则 make 管理器就是找到sunq目标体开始执行。
这时,make 会自动检查相关文件的时间戳。首先,在检查"kang.o"、"yul.o"和"sunq"3个文件的时间戳之前,它会向下查找那些把"kang.o"或"yul.o"作为目标文件的时间戳。比如,“kang.o”的依赖文件为“kang.c”、“kang.h” 、“head.h”。如果这些文件中任何一个的
时间戳比“kang.o”新,则命令“gcc –Wall –O -g –c kang.c -o kang.o”将会执行,从而更新文件“kang.o” 。在更新完“kang.o”或“yul.o”之后,make 会检查最初的“kang.o”、“yul.o”和“sunq”3 个文件,只要文件“kang.o”或“yul.o”中的任比文件时间戳比“sunq”新,则第二行命令就会被执行。这样,make 就完成了自动检查时间戳的工作,开始执行编译工作。这也就是 Make 工作的基本流程。
接下来,为了进一步简化编辑和维护 Makefile,make 允许在 Makefile 中创建和使用变量。变量是在 Makefile 中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标体、依赖文件、命令以及 makefile 文件中其他部分。在Makefile 中的变量定义有两种方式:一种是递归展开方式,另一种是简单方式。
递归展开方式定义的变量是在引用在该变量时进行替换的,即如果该变量包含了对其他变量的应用,则在引用该变量时一次性将内嵌的变量全部展开,虽然这种类型的变量能够很好地完成用户的指令,但是它也有严重的缺点,如不能在变量后追加内容(因为语句:CFLAGS = $(CFLAGS) -O 在变量扩展过程中可能导致无穷循环)。
为了避免上述问题,简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量的引用,从而消除变量的嵌套引用。
递归展开方式的定义格式为:VAR=var。
简单扩展方式的定义格式为:VAR:=var。
Make 中的变量使用均使用格式为:$(VAR)。
注意:变量名是不包括“:”、“#”和“=”结尾空格的任何字符串。同时,变量名中包含字母、数字以及下划线以外的情况应尽量避免,因为它们可能在将来被赋予特别的含义。变量名是大小写敏感的,例如变量名“foo”、“FOO”和“Foo”代表不同的变量。推荐在 makefile 内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
下面给出了上例中用变量替换修改后的 Makefile,这里用 OBJS 代替 kang.o 和 yul.o,用CC 代替 Gcc,用 CFLAGS 代替“-Wall -O –g” 。这样在以后修改时,就可以只修改变量定义,而不需要修改下面的定义实体,从而大大简化了 Makefile 维护的工作量。
经变量替换后的 Makefile 如下所示:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
下面给出了上例中用变量替换修改后的 Makefile,这里用 OBJS 代替 kang.o 和 yul.o,用CC 代替 Gcc,用 CFLAGS 代替“-Wall -O –g” 。这样在以后修改时,就可以只修改变量定义,而不需要修改下面的定义实体,从而大大简化了 Makefile 维护的工作量。
经变量替换后的 Makefile 如下所示:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
可以看到,此处变量是以递归展开方式定义的。
Makefile 中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。如上例中的 OBJS 就是用户自定义变量,自定义变量的值由用户自行设定,而预定义变量和自动变量为通常在 Makefile 都会出现的变量,其中部分有默认值,也就是常见的设定值,当然用户可以对其进行修改。
预定义变量包含了常见编译器、汇编器的名称及其编译选项。下面列出了 Makefile中常见预定义变量及其部分默认值。
Makefile 中常见预定义变量
命 令 格 式 含 义
AR 库文件维护程序的名称,默认值为 ar
AS 汇编程序的名称,默认值为 as
CC C 编译器的名称,默认值为 cc
CPP C 预编译器的名称,默认值为$(CC) –E
CXX C++编译器的名称,默认值为 g++
FC FORTRAN 编译器的名称,默认值为 f77
RM 文件删除程序的名称,默认值为 rm –f
ARFLAGS 库文件维护程序的选项,无默认值
ASFLAGS 汇编程序的选项,无默认值
CFLAGS C 编译器的选项,无默认值
CPPFLAGS C 预编译的选项,无默认值
CXXFLAGS C++编译器的选项,无默认值
FFLAGS FORTRAN 编译器的选项,无默认值
可以看出,上例中的 CC 和 CFLAGS 是预定义变量,其中由于 CC 没有采用默认值,因此,需要把“CC=Gcc”明确列出来。
由于常见的 Gcc 编译语句中通常包含了目标文件和依赖文件,而这些文件在 Makefile 文件中目标体的一行已经有所体现,因此,为了进一步简化 Makefile 的编写,就引入了自动变量。自动变量通常可以代表编译语句中出现目标文件和依赖文件等,并且具有本地含义(即下一语句中出现的相同变量代表的是下一语句的目标文件和依赖文件)。
Makefile 中常见自动变量。
命 令 格 式 含 义
$* 不包含扩展名的目标文件名称
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含 重复的依赖文件
$< 第一个依赖文件的名称
$? 所有时间戳比目标文件晚的依赖文件,并以空格分开
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分开
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
自动变量的书写比较难记,但是在熟练了之后会非常的方便,请读者结合下例中的自动变量改写的 Makefile 进行记忆。
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c $< -o $@
另外,在 Makefile 中还可以使用环境变量。使用环境变量的方法相对比较简单,make在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量。但是,如果用户在 Makefile 中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。
3.Makefile 规则
Makefile 的规则是 Make 进行处理的依据,它包括了目标体、依赖文件及其之间的命令语句。一般的,Makefile 中的一条语句就是一个规则。在上面的例子中,都显示地指出了Makefile 中的规则关系,如“$(CC) $(CFLAGS) -c $< -o $@”,但为了简化 Makefile 的编写,make 还定义了隐式规则和模式规则,下面就分别对其进行讲解。
1.隐式规则
隐含规则能够告诉 make 怎样使用传统的技术完成任务,这样,当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可。Make 会自动搜索隐式规则目录来确定如何生成目标文件。如上例就可以写成:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
为什么可以省略后两句呢?因为 Make 的隐式规则指出:所有“.o”文件都可自动由“.c”文件使用命令“$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c –o file.o”生成。这样“kang.o”和“yul.o”就会分别调用“$(CC) $(CFLAGS) -c kang.c -o kang.o”和“$(CC) $(CFLAGS) -c yul.c
-o yul.o”生成。
注意:在隐式规则只能查找到相同文件名的不同后缀名文件,如“kang.o”文件必须由“kang.c”文件生成。
Makefile 中常见隐式规则目录
对应语言后缀名 规 则
C 编译:.c 变为.o $(CC) –c $(CPPFLAGS) $(CFLAGS)
C++编译:.cc 或.C 变为.o $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
Pascal 编译:.p 变为.o $(PC) -c $(PFLAGS)
Fortran 编译:.r 变为-o $(FC) -c $(FFLAGS)
2.模式规则
模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则,隐式规则仅仅能够用 make 默认的变量来进行操作,而模式规则还能引入用户自定义变量,为多个文件建立相同的规则,从而简化 Makefile 的编写。
模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明。使用模式规则修改后的 Makefile 的编写如下:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
4.Make 管理器的使用
使用 Make 管理器非常简单,只需在 make 命令的后面键入目标名即可建立指定的目标,如果直接运行 make,则建立 Makefile 中的第一个目标。
此外 make 还有丰富的命令行选项,可以完成各种不同的功能。
make 的命令行选项
命 令 格 式 含 义
-C dir 读入指定目录下的 Makefile
-f file 读入当前目录下的 file 文件作为 Makefile
-i 忽略所有的命令执行错误
-I dir 指定被包含的 Makefile 所在目录
-n 只打印要执行的命令,但不执行这些命令
-p 显示 make 变量数据库和隐含规则
-s 在执行命令时不显示命令
-w 如果 make 在执行过程中改变目录,则打印当前目录名
Makefile 是 Make 读入的惟一配置文件,因此本节的内容实际就是讲述 Makefile 的编写规则。在一个 Makefile 中通常包含如下内容:
1)需要由 make 工具创建的目标体(target),通常是目标文件或可执行文件;
2)要创建的目标体所依赖的文件(dependency_file);
3)创建每个目标体时需要运行的命令(command)。
它的格式为:
target: dependency_files
command
注意:在 Makefile 中的每一个 command 前必须有“Tab”符,否则在运行 make 命令时会出错。
例如,有两个文件分别为 hello.c 和 hello.h,创建的目标体为 hello.o,执行的命令为 gcc。编译指令:gcc –c hello.c,那么,对应的 Makefile 就可以写为:
#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c –o hello.o
接着就可以使用 make了。使用make 的格式为:make target,这样 make 就会自动读入Makefile(也可以是首字母小写 makefile)并执行对应 target 的 command 语句,并会找到相应的依赖文件。如下所示:
[root@localhost makefile]# make hello.o
gcc –c hello.c –o hello.o
[root@localhost makefile]# ls
hello.c hello.h hello.o Makefile
可以看到,Makefile 执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。
2.Makefile 变量
上面示例的 Makefile 在实际中是几乎不存在的,因为它过于简单,仅包含两个文件和一个命令,在这种情况下完全不必要编写 Makefile 而只需在 Shell 中直接输入即可,在实际中使用的 Makefile 往往是包含很多的文件和命令的,这也是 Makefile 产生的原因。下面就可给出稍微复杂一些的 Makefile 进行讲解:
sunq:kang.o yul.o
Gcc kang.o bar.o -o myprog
kang.o : kang.c kang.h head.h
Gcc –Wall –O -g –c kang.c -o kang.o
yul.o : bar.c head.h
Gcc - Wall –O -g –c yul.c -o yul.o
在这个 Makefile 中有 3 个目标体(target),分别为sunq、kang.o和yul.o,其中第一个目标体的依赖文件就是后两个目标体。如果用户使用命令“make sunq”,则 make 管理器就是找到sunq目标体开始执行。
这时,make 会自动检查相关文件的时间戳。首先,在检查"kang.o"、"yul.o"和"sunq"3个文件的时间戳之前,它会向下查找那些把"kang.o"或"yul.o"作为目标文件的时间戳。比如,“kang.o”的依赖文件为“kang.c”、“kang.h” 、“head.h”。如果这些文件中任何一个的
时间戳比“kang.o”新,则命令“gcc –Wall –O -g –c kang.c -o kang.o”将会执行,从而更新文件“kang.o” 。在更新完“kang.o”或“yul.o”之后,make 会检查最初的“kang.o”、“yul.o”和“sunq”3 个文件,只要文件“kang.o”或“yul.o”中的任比文件时间戳比“sunq”新,则第二行命令就会被执行。这样,make 就完成了自动检查时间戳的工作,开始执行编译工作。这也就是 Make 工作的基本流程。
接下来,为了进一步简化编辑和维护 Makefile,make 允许在 Makefile 中创建和使用变量。变量是在 Makefile 中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标体、依赖文件、命令以及 makefile 文件中其他部分。在Makefile 中的变量定义有两种方式:一种是递归展开方式,另一种是简单方式。
递归展开方式定义的变量是在引用在该变量时进行替换的,即如果该变量包含了对其他变量的应用,则在引用该变量时一次性将内嵌的变量全部展开,虽然这种类型的变量能够很好地完成用户的指令,但是它也有严重的缺点,如不能在变量后追加内容(因为语句:CFLAGS = $(CFLAGS) -O 在变量扩展过程中可能导致无穷循环)。
为了避免上述问题,简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量的引用,从而消除变量的嵌套引用。
递归展开方式的定义格式为:VAR=var。
简单扩展方式的定义格式为:VAR:=var。
Make 中的变量使用均使用格式为:$(VAR)。
注意:变量名是不包括“:”、“#”和“=”结尾空格的任何字符串。同时,变量名中包含字母、数字以及下划线以外的情况应尽量避免,因为它们可能在将来被赋予特别的含义。变量名是大小写敏感的,例如变量名“foo”、“FOO”和“Foo”代表不同的变量。推荐在 makefile 内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
下面给出了上例中用变量替换修改后的 Makefile,这里用 OBJS 代替 kang.o 和 yul.o,用CC 代替 Gcc,用 CFLAGS 代替“-Wall -O –g” 。这样在以后修改时,就可以只修改变量定义,而不需要修改下面的定义实体,从而大大简化了 Makefile 维护的工作量。
经变量替换后的 Makefile 如下所示:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
下面给出了上例中用变量替换修改后的 Makefile,这里用 OBJS 代替 kang.o 和 yul.o,用CC 代替 Gcc,用 CFLAGS 代替“-Wall -O –g” 。这样在以后修改时,就可以只修改变量定义,而不需要修改下面的定义实体,从而大大简化了 Makefile 维护的工作量。
经变量替换后的 Makefile 如下所示:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
可以看到,此处变量是以递归展开方式定义的。
Makefile 中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。如上例中的 OBJS 就是用户自定义变量,自定义变量的值由用户自行设定,而预定义变量和自动变量为通常在 Makefile 都会出现的变量,其中部分有默认值,也就是常见的设定值,当然用户可以对其进行修改。
预定义变量包含了常见编译器、汇编器的名称及其编译选项。下面列出了 Makefile中常见预定义变量及其部分默认值。
Makefile 中常见预定义变量
命 令 格 式 含 义
AR 库文件维护程序的名称,默认值为 ar
AS 汇编程序的名称,默认值为 as
CC C 编译器的名称,默认值为 cc
CPP C 预编译器的名称,默认值为$(CC) –E
CXX C++编译器的名称,默认值为 g++
FC FORTRAN 编译器的名称,默认值为 f77
RM 文件删除程序的名称,默认值为 rm –f
ARFLAGS 库文件维护程序的选项,无默认值
ASFLAGS 汇编程序的选项,无默认值
CFLAGS C 编译器的选项,无默认值
CPPFLAGS C 预编译的选项,无默认值
CXXFLAGS C++编译器的选项,无默认值
FFLAGS FORTRAN 编译器的选项,无默认值
可以看出,上例中的 CC 和 CFLAGS 是预定义变量,其中由于 CC 没有采用默认值,因此,需要把“CC=Gcc”明确列出来。
由于常见的 Gcc 编译语句中通常包含了目标文件和依赖文件,而这些文件在 Makefile 文件中目标体的一行已经有所体现,因此,为了进一步简化 Makefile 的编写,就引入了自动变量。自动变量通常可以代表编译语句中出现目标文件和依赖文件等,并且具有本地含义(即下一语句中出现的相同变量代表的是下一语句的目标文件和依赖文件)。
Makefile 中常见自动变量。
命 令 格 式 含 义
$* 不包含扩展名的目标文件名称
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含 重复的依赖文件
$< 第一个依赖文件的名称
$? 所有时间戳比目标文件晚的依赖文件,并以空格分开
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分开
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
自动变量的书写比较难记,但是在熟练了之后会非常的方便,请读者结合下例中的自动变量改写的 Makefile 进行记忆。
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c $< -o $@
另外,在 Makefile 中还可以使用环境变量。使用环境变量的方法相对比较简单,make在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量。但是,如果用户在 Makefile 中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。
3.Makefile 规则
Makefile 的规则是 Make 进行处理的依据,它包括了目标体、依赖文件及其之间的命令语句。一般的,Makefile 中的一条语句就是一个规则。在上面的例子中,都显示地指出了Makefile 中的规则关系,如“$(CC) $(CFLAGS) -c $< -o $@”,但为了简化 Makefile 的编写,make 还定义了隐式规则和模式规则,下面就分别对其进行讲解。
1.隐式规则
隐含规则能够告诉 make 怎样使用传统的技术完成任务,这样,当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可。Make 会自动搜索隐式规则目录来确定如何生成目标文件。如上例就可以写成:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
为什么可以省略后两句呢?因为 Make 的隐式规则指出:所有“.o”文件都可自动由“.c”文件使用命令“$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c –o file.o”生成。这样“kang.o”和“yul.o”就会分别调用“$(CC) $(CFLAGS) -c kang.c -o kang.o”和“$(CC) $(CFLAGS) -c yul.c
-o yul.o”生成。
注意:在隐式规则只能查找到相同文件名的不同后缀名文件,如“kang.o”文件必须由“kang.c”文件生成。
Makefile 中常见隐式规则目录
对应语言后缀名 规 则
C 编译:.c 变为.o $(CC) –c $(CPPFLAGS) $(CFLAGS)
C++编译:.cc 或.C 变为.o $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
Pascal 编译:.p 变为.o $(PC) -c $(PFLAGS)
Fortran 编译:.r 变为-o $(FC) -c $(FFLAGS)
2.模式规则
模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则,隐式规则仅仅能够用 make 默认的变量来进行操作,而模式规则还能引入用户自定义变量,为多个文件建立相同的规则,从而简化 Makefile 的编写。
模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明。使用模式规则修改后的 Makefile 的编写如下:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
4.Make 管理器的使用
使用 Make 管理器非常简单,只需在 make 命令的后面键入目标名即可建立指定的目标,如果直接运行 make,则建立 Makefile 中的第一个目标。
此外 make 还有丰富的命令行选项,可以完成各种不同的功能。
make 的命令行选项
命 令 格 式 含 义
-C dir 读入指定目录下的 Makefile
-f file 读入当前目录下的 file 文件作为 Makefile
-i 忽略所有的命令执行错误
-I dir 指定被包含的 Makefile 所在目录
-n 只打印要执行的命令,但不执行这些命令
-p 显示 make 变量数据库和隐含规则
-s 在执行命令时不显示命令
-w 如果 make 在执行过程中改变目录,则打印当前目录名