makefile编写教程

来源:互联网 发布:网页版的淘宝无法登陆 编辑:程序博客网 时间:2024/06/06 11:00

目录

  • 1. 版权声明
  • 2. 一个原始示例
  • 3. makefile语法基本规则
  • 4. makefile如何工作的
  • 5. makefile样例代码优化
    • 5.1. 使用变量,减少字符串重复使用
    • 5.2. 静态模式
    • 5.3. 使用隐含规则
    • 5.4. 多目标消除重复的[.h]
    • 5.5. 标志多目标的自动化变量
  • 6. makefile组成
    • 6.1. 显式规则
    • 6.2. 隐含规则
      • 6.2.1. 打印make的所有隐含规则
      • 6.2.2. 使用隐含规则
      • 6.2.3. 隐含规则使用的变量
      • 6.2.4. 定义模式规则
  • 7. 伪目标
  • 8. 变量
    • 8.1. 命名规则
    • 8.2. 通配符
    • 8.3. “<”表示所有的依赖目标集,“@”表示目标集
    • 8.4. VPATH变量
  • 9. 关键字
    • 9.1. 通配符
    • 9.2. vpath
  • 10. 显示makefile执行的命令
  • 11. 连续执行命令

版权声明

跟我一起写 Makefile 作者:陈皓
本文是这篇文章的读书笔记,综合其他资料加了一些解释,使读者更容易明白

一个原始示例

这里写图片描述

如图所示,文件目录中代码如下
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

makefile语法基本规则

target … : prerequisites …
command

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个 Tab键 作为开头。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。

makefile如何工作的

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

  1. 读入所有的Makefile。
  2. 读入被include的其它Makefile。
  3. 初始化文件中的变量。
  4. 推导隐晦规则,并分析所有规则。
  5. 为所有的目标文件创建依赖关系链。
  6. 根据依赖关系,决定哪些目标要重新生成。
  7. 执行生成命令。

在默认的方式下,也就是我们只输入make命令。那么,

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
  3. 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的存在,但是文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
  4. 如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

makefile样例代码优化

使用变量,减少字符串重复使用

原始:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
优化后:
edit : (objects)ccoedit(objects)

静态模式

objects = foo.o bar.o
all: (objects)(objects): %.o: %.c
(CC)c(CFLAGS) <o@
上面的例子中,指明了我们的目标从objectobject集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“<@”则是自动化变量,“<foo.cbar.c@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c
(CC)c(CFLAGS) foo.c -o foo.o
bar.o : bar.c
(CC)c(CFLAGS) bar.c -o bar.o
试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。

使用隐含规则

只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : (objects)ccoedit(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)

多目标消除重复的[.h]

objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : (objects)ccoedit(objects)
(objects):defs.hkbd.ocommand.ofiles.o:command.hdisplay.oinsert.osearch.ofiles.o:buffer.h.PHONY:cleanclean:rmedit(objects)

我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o文件,那就理不清楚了。

标志多目标的自动化变量

bigoutput littleoutput : text.g
generate text.g -(substoutput,,@) > $@

上述规则等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
其中,-(substoutput,,\(@)\)Makefilesubst@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

makefile组成

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

显式规则

显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

隐含规则

“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。

打印make的所有隐含规则

make –p
如系统变量“CFLAGS”可以控制编译时的编译器参数。

使用隐含规则

如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile:
foo : foo.o bar.o
cc –o foo foo.o bar.o (CFLAGS)(LDFLAGS)
我们可以注意到,这个Makefile中并没有写下如何生成foo.o和bar.o这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。
make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在上面的那个例子中,make调用的隐含规则是,把[.o]的目标
的依赖文件置成[.c],并使用C的编译命令“cc –c (CFLAGS)[.c][.o]foo.o:foo.ccccfoo.c(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器“cc”生成[.o]文件的规则,这就是隐含规则。
当然,如果我们为[.o]文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行。
还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令):
foo.o : foo.p
依赖文件“foo.p”(Pascal程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么我们的隐含规则一样会生效,并会通过“foo.c”调用C的编译器生成foo.o文件。因为,在隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成foo.o的C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。

隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。
例如,第一条隐含规则——编译C程序的隐含规则的命令是“(CC)c(CFLAGS) ((CPPFLAGS)”。Make默认的编译命令是“cc”,如果你把变量“)(CC)”重定义成“gcc”,把变量“(CFLAGS)ggcccg(CPPFLAGS)”的样子来执行了。
把隐含规则中使用的变量分成两种:一种是命令相关的,如“CC”;一种是参数相的关,如“CFLAGS”。

  1. 关于命令的变量

    AR
    函数库打包程序。默认命令是“ar”。
    AS
    汇编语言编译程序。默认命令是“as”。
    CC
    C语言编译程序。默认命令是“cc”。

  2. 关于命令参数的变量

    CFLAGS
    C语言编译器参数。
    CXXFLAGS
    C++语言编译器参数。

定义模式规则

你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有”%”字符。”%”的意思是表示一个或多个任意字符。在依赖目标中同样可以使用”%”,只是依赖目标中的”%”的取值,取决于其目标。
有一点需要注意的是,”%”的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入Makefile时,而模式规则中的”%”则发生在运行时。

  1. 模式规则介绍

    模式规则中,至少在规则的目标定义中要包含”%”,否则,就是一般的规则。目标中的”%”定义表示对文件名的匹配,”%”表示长度任意的非空字符串。例如:”%.c”表示以”.c”结尾的文件名(文件名的长度至少为3),而”s.%.c”则表示以”s.”开头,”.c”结尾的文件名(文件名的长度至少为5)。
    如果”%”定义在目标中,那么,目标中的”%”的值决定了依赖目标中的”%”的值,也就是 说,目标中的模式的”%”决定了依赖目标中”%”的样子。例如有一个模式规则如下:
    %.o : %.c ;

伪目标

clean:
rm edit (objects).PHONY:cleanclean:rmedit(objects)
.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。clean的规则不要放在文件的开头,不然,这就会变成make的默认目标。 “clean从来都是放在文件的最后”。 当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

  1. 表示一个变量为伪目标
  2. 利用伪目标生成多个可执行文件
  3. 利用伪目标定点删除文件,而不是删除所有

变量

命名规则

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“(”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“)”字符,那么你需要用“$$”来表示。

通配符

objects = .o//objects的值为.o
objects := $(wildcard *.o) //objects的值为所有[.o]的文件名的集合,这种用法由关键字“wildcard”指出

<@”表示目标集

VPATH变量

Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)

关键字

通配符

objects = .o//objects的值为.o
objects := $(wildcard *.o) //objects的值为所有[.o]的文件名的集合,这种用法由关键字“wildcard”指出

vpath

另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:

  1. vpath
    为符合模式的文件指定搜索目录。
  2. vpath
    清除符合模式的文件的搜索目录。
  3. vpath
    清除所有已被设置好了的文件搜索目录。
    vapth使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的目录。例如:
    vpath %.h ../headers
    该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
    我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索。如:
    vpath %.c foo
    vpath % blish
    vpath %.c bar
    其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
    vpath %.c foo:bar
    vpath % blish
    而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。

显示makefile执行的命令

@echo 正在编译XXX模块……
当make执行时,会输出“正在编译XXX模块……”字串,但不会输出命令。

如果没有“@”,那么,make将输出:
echo 正在编译XXX模块……
正在编译XXX模块……

连续执行命令

需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。
示例一:
exec:
cd /home/hchen
pwd
示例二:
exec:
cd /home/hchen; pwd
当我们执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。