make与Makefile

Makefile 包含了所有的规则和目标,而 make 则是为了完成目标而去解释 Makefile 规则的工具

make语法
make [ -f makefile ] [ options ] … [ targets ] …

前期准备

无论是c/c++都是由源文件编译为目标文件,Windows上.obj,UNIX上.o文件,即Object File文件。然后再把大量的Object File合成执行文件,此过程交链接。

makefile介绍

make命令执行时,需要一个makefile文件,用以告诉make命令需要如何去链接和编译

makefile规则

target ... : prerequisites ...
    command

target
可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。

prerequisites
生成该target所依赖的文件和/或target

command
该target要执行的命令(任意的shell命令)

示例:
如果一个工程有3个头文件和8个c文件,为了完成前面所述的那三个规则,我们的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的阅读。我们可以把这个内容保存在名字 为“makefile”或“Makefile”的文件中,然后在该目录下直接输入命令 make 就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下 make clean 就可以了。
make会比较targets文件 和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或 者target不存在的话,那么,make就会执行后续定义的命令。
这里要说明一点的是, clean 不是一个文件,它只不过是一个动作名字,有点像c语言中的label一 样,其冒号后什么也没有,那么,make就不会自动去找它的依赖性,也就不会自动执行其后所定义的命令。 要执行其后的命令,就要在make命令后明显得指出这个label的名字。这样的方法非常有用,我们可以在一 个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等

make是如何工作

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

在makefile中使用变量

在makefile中可以使用变量来简化makefile的编辑。
如上边的例子中可以将edit依赖的文件用变量表示,用来减轻工作量。

objects = main.o kbd.o command.o display.o \
     insert.o search.o files.o utils.o

经过使用变量改良后的例子:

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(objects)
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 $(objects)

make自动推导

只要make看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中,如果make找到一个 whatever.o ,那么 whatever.c 就会是 whatever.o 的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的 新makefile又出炉了。

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(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)

清空目标文件规则

例子中

clean:
    rm edit $(objects)

更稳健的方法

.PHONY : clean
clean :
    -rm edit $(objects)

前面说过, .PHONY 表示 clean 是一个“伪目标”。而在 rm 命令前面加了一个小减号的 意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然, clean 的规则不要放在文件 的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放 在文件的最后”。

makefile可以有什么

  1. 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者明显指出要生成的 文件、文件的依赖文件和生成的命令。
  2. 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写 Makefile,这是由make所支持的。
  3. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的 宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
  4. 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中 的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一 样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
  5. 注释。#

引用其他的makefile

使用 include 关键字可以把别的Makefile包含进来
在 include 前面可以有一些空字符,但是绝不能是 Tab 键开始。
make命令开始时,会找寻 include 所指出的其它Makefile,并把其内容安置在当前的位置。就好 像C/C++的 #include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目 录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

  1. 如果make执行时,有 -I 或 –include-dir 参数,那么make就会在这个参数所指定的目 录下去寻找。
  2. 如果目录 \/include (一般是: /usr/local/bin 或 /usr/include )存在的话,make也会去找。
    如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的 文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是 不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以 在include前加一个减号“-”。如:
-include <filename>

其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令 是sinclude,其作用和这一个是一样的。

make的工作方式

GNU的make工作时的执行步骤如下:(想来其它的make也是类似)
1. 读入所有的Makefile。
2. 读入被include的其它Makefile。
3. 初始化文件中的变量。
4. 推导隐晦规则,并分析所有规则。
5. 为所有的目标文件创建依赖关系链。
6. 根据依赖关系,决定哪些目标要重新生成。
7. 执行生成命令。

书写规则

语法

targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能 是多个文件。

command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以 Tab 键开头,如 果和prerequisites在一行,那么可以用分号做为分隔。(见上)

prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么, 目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。

在规则中使用通配符

如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三个通配符: * , ? 和 ~ 。
波浪号( ~ )字符在文件名中也有比较特殊的用途。如果是 ~/test ,这就表示当前用户 的 $HOME 目录下的test目录。而 ~hchen/test 则表示用户hchen的宿主目录下的test 目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是 MS-DOS下,用户没有宿主目录, 那么波浪号所指的目录则根据环境变量“HOME”而定。

objects = *.o

上面这个例子,表示了通配符同样可以用在变量中。并不是说 *.o 会展开,不!objects的值就是 *.o 。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是 让objects的值是所有 .o 的文件名的集合,那么,你可以这样:

objects := $(wildcard *.o)

文件搜索

在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。 所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告 诉make,让make在自动去找。

Makefile文件中的特殊变量 VPATH 就是完成这个功能的,如果没有指明这个变量,make只会在当前 的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下 ,到所指定的目录中去找寻文件了。

VPATH = src:../headers

上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔 。(当然,当前目录永远是最高优先搜索的地方)

伪目标

可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“ 伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

条件判断

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(objects) $(normal_libs)
endif

我们可以从上面的示例中看到三个关键字: ifeq 、 else 和 endif 。 ifeq 的 意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括 起。 else 表示条件表达式为假的情况。 endif 表示一个条件语句的结束,任何一个条件表达 式都应该以 endif 结束。

当我们的变量 (CC)gccfoofoo: ( C C ) 值 是 g c c 时 , 目 标 f o o 的 规 则 是 : f o o : (objects)
(CC)ofoo ( C C ) − o f o o (objects) $(libs_for_gcc)

而当我们的变量 (CC)gccccfoofoo: ( C C ) 值 不 是 g c c 时 ( 比 如 c c ) , 目 标 f o o 的 规 则 是 : f o o : (objects)
(CC)ofoo ( C C ) − o f o o (objects) $(normal_libs)

语法

<conditional-directive>
<text-if-true>
endif

and

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中 表示条件关键字,如 ifeq 。这个关键字有四个。
1. ifeq 比较参数 arg1 和 arg2 的值是否相同。
2. ifneq 其比较参数 arg1 和 arg2 的值是否相同,如果不同,则为真。
3. ifdef 如果变量 的值非空,那到表达式为真。否则,表达式为假。 同样可以是一个函数的返回值。
4. ifndef 在 这一行上,多余的空格是被允许的,但是不能以 Tab 键 作为开始(不然就被认为是命令)。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值