25 January 2015

makefile主要是把需要编译的源文件的命令集合到一个文件中,这样就不需要每次都把文件依次编译一遍,而只需执行这个文件就可以一次性编译,生成可执行文件。其实也可以看作成一个shell命令

同时他也定义了一系列的规则, 比如先编译文件的先后顺序,它实现的是自动化编译过程,一旦写好了,只需一个make命令,整个工程就自动编译,提供了我们开发的效率(但是再windows下没有这个工具,因为我们的IDE已经为我们做了这一步)。

这里的话,可以说一下代码编译过程,对于c/c++程序首先需要将源文件编译成中间目标代码文件(.o object file),这个就是编译(compile)

然后把大量的Object file合成可执行文件,这个动作就是链接(link)

在链接时,主要是链接函数和全局变量,故我们使用这些中间代码文件来链接我们的应用程序。在链接的时候连接器不管函数所在的源文件,只管函数的中间目标文件,在大多数情况下,源文件太多,编译生成的中间目标文件太多,而在链接时,需要明显地指出中间目标文件名,但是由于编译的不方便,一般的话都会给中间目标文件打个包,在windows下就是库文件(.lib library file),在unix下是.a Archive file

makefile的一些基本规则

makefile文件有三部分: - target:目标文件,就是最后的执行文件 - prerequisite:要生成target所需要的文件或是目标(依赖文件) - command:make需要执行的命令

例如下面一个工程里有三个头文件,8个c文件

defs.h.command.h,buffer.h

main.c,kdb.c,command.c,display.c,insert.c,search.c,files.c,utils.c

edit : main.o,kdb.o,command.o,display.o,insert.o,\ 
        search.o,files.o,utils.o                      /*如果后面这些.o文件比edit可执行文件新,那么才会去执行下面这句命令*/
        gcc -o edit main.o,kdb.o,command.o,display.o,insert.o,\ 
        search.o,files.o,utils.o
main.o : main.c defs.h
        gcc -c main.c
kdb.o : kdb.c defs.h
        gcc -c kdb.c
command.o : command.c defs.h command.h
        gcc -c command.c
display.o : display.c defs.h buffer.h
        gcc -c display.c
insert.o : insert.c defs.h buffer.h
        gcc -c insert.c
search.o : search.c defs.h buffer.h
        gcc -c search.c
files.o : files.c defs.h buffer.h command.h
        gcc -c files.c
utils.o : utils.c defs.h
        gcc -c utils.c

clean:
        rm edit main.o,kdb.o,command.o,display.o,insert.o,\
        search.o,files.o,utils.o

这段makefile的只有执行make就会生成执行文件edit,如果需要删除中间目标文件只需执行make clean

  • target:edit和中间目标文件(*.o)
  • prerequisite:冒号(:)后面的.c和.h文件

make工作流程

  1. make会在当前目录下找名字为Makefilemakefile文件
  2. 如果找到,找到文件中的第一个目标文件(target),如上面例子:edit文件
  3. 如果edit文件不存在,或者edit所依赖的后面的.o文件的文件修改时间比edit这个文件新,那么,他就执行后面需要的命令生成edit文件
  4. 如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则会根据依赖关系生成.o文件
  5. 当你的C文件和H文件都存在时,于是make会生成.o文件,然后再用.o文件生成make的最终可执行文件edit

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那还是会直接退出。

在编译通过之后,当我们修改了其中一个源文件,比如files.c文件,那么根据我们的依赖关系,我们的目标文件files.o会重新编译一遍,于是files.o是最新的,但是edit还不是最新,所有edit也会重新链接。

如果我们修改了"command.h",那么kdb.o,command.o,files.o都会重新编译,并且edit会重新链接。

makefile中的变量

在makefile中一些文件需要被重复使用,那么我们就可以使用变量来对他们进行声明和使用:

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

在使用变量之后一些表达式就可以变得非常简单可用了,在makefile里面使用变量以$(objects)这种方式来使用变量。下面我们就可以把上面的例子简写成:

objects = main.o,kdb.o,command.o,display.o,\
          insert.o, search.o,files.o,utils.o 
edit : $(objects)
        gcc -o edit $(objects)
main.o : main.c defs.h
        gcc -c main.c
kdb.o : kdb.c defs.h
        gcc -c kdb.c
command.o : command.c defs.h command.h
        gcc -c command.c
display.o : display.c defs.h buffer.h
        gcc -c display.c
insert.o : insert.c defs.h buffer.h
        gcc -c insert.c
search.o : search.c defs.h buffer.h
        gcc -c search.c
files.o : files.c defs.h buffer.h command.h
        gcc -c files.c
utils.o : utils.c defs.h
        gcc -c utils.c

clean:
        rm edit $(objects)

makefile 自动推导功能

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

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

objects = main.o,kdb.o,command.o,display.o,\
          insert.o, search.o,files.o,utils.o 
edit : $(objects)
        gcc -o edit $(objects)
main.o : defs.h
kdb.o : defs.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

.PHONE : clean
clean:
        rm edit $(objects)

.PHONE表示clean是一个伪目标文件

makefile文件里有什么

makefile文件主要有5个东西:显式规则,隐晦规则,变量定义,文件指示和注释。

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

另外:在Makefile中的命令,必须要以Tab键开始。

引用其他的MakeFile

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是: makefile include <filename> 在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和;可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk,b.mk,c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了 e.mk和f.mk,那么,下面的语句: makefile include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make的工作方式:

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

1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

参考