Makefile总结

 

gcc编译流程

1、编写C程序源代码

      2、预处理 (加入文件到源文件 include,)

      3、编译 (目标文件.o)

4、链接  (可执行文件)   

gcc  选项

-c
编译、汇编到目标代码(.o),不进行链接。如上图所示。

 

-o outfile
输出到指定的文件。

 

Makefile的内容

Makefile里主要包含:

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的文件名

大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

三、makefile如何知道自己该干嘛

一句话:从目标到依赖关系,从依赖关系到目标,再从目标到依赖关系。

四、引用其它的Makefile

include的语法是:

    include <filename>

注意事项:filename可以是当前操作系统Shell的文件模式(可以包含路径和通配符)

include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和<filename>可以用一个或多个空格隔开。

如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

    1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。

    2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

五、环境变量 MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响。这也提醒我们,也许有时候你的Makefile出现了怪事,那时候就可以看看当前环境中有没有定义这个变量。如果是的话就只需要取消那些环境变量即可。

六、makefile的的工作过程总结

GNU的make工作时的执行步骤如下:

    1、读入所有的Makefile。

    2、读入被include的其它Makefile。

    3、初始化文件中的变量。

    4、推导隐晦规则,并分析所有规则。

    5、为所有的目标文件创建依赖关系链。

    6、根据依赖关系,决定哪些目标要重新生成。

    7、执行生成命令。

其中1-5为第一阶段,6、7为第二阶段。第一阶段中,如果定义跌变量被使用,那么make会把它展开在使用的位置,如果定义出现在依赖关系之中,那么仅当这条依赖被决定需要使用了,变量才会在其内部展开。

Makefile一般采用的结构式从上而下,下层目标是用来让上层目标保持在最新状态。

 

简单程序介绍:

一、一个makefile的实例

test:main.o
        gcc -o test main.o

main.o:main.c
        gcc -c main.c -o main.o

clean:
        @rm -vf main.o test

 

二、Makefile的书写规则

1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

    2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

    3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

规则包括两部分,一个是依赖关系,一个是生成目标的方法。

在Makefile中,规则的顺序是很重要的,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多(至少有两个目标),但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。整个make所完成的也就是这个目标。

三、规则举例

    foo.o : foo.c defs.h

            gcc -c -g foo.c

这个规则告诉我们两件事:

1、文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的时间戳要比foo.o的要新,或是foo.o不存在,那么依赖关系发生。

    2、如果生成(或更新)foo.o文件。也就是那个gcc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)

四、规则的语法

targets : prerequisites

        command

        ...

      或是这样:

      targets : prerequisites ; command

            command

            ...

Targets(即目标)多数情况下是文件名,以空格分开,可以使用通配符。目标基本上是一个文件,也可以是许多个文件。

注意:目标可以使用任何名称,并不一定非得是真实的文件名。

command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。第二种写法,目前见过的大多数都采用第一种。

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

注意:当冒号的右边没有指定依赖关系时会怎么样——只有在目标代表的文件不存在时才会进行更新的动作。

如果命令太长(这涉及到代码的规范化问题,即增进代码的可读性),你可以使用反斜框(‘\’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。

五、在规则中使用通配符

如果我们想定义一系列比较类似的文件,即可使用通配符,当意欲创建适应能力较强的makefile时,通配符就很有用了。make支持三个通配符:“*”,“?”和“[...]”。这是和Unix的B-Shell是相同的。波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。通配符代替了一系列的文件,如“*.c”表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。
    一个例子:

clean:
              rm -f *.o
      很常见的,这是操作系统Shell所支持的通配符。这是在命令中的通配符。
    print: *.c
              lpr -p $?
              touch print
       上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的[.c]文件。注意:其中的“$?”是一个自动化变量。
      objects = *.o
    上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:
    objects := $(wildcard *.o)
    这种用法由关键字“wildcard”指出。

wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。

六、文件搜寻

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

方法一:

Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。

VPATH = src:../headers

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

方法二:

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

1、vpath <pattern>; <directories>;

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

 

七、伪目标(也称“假想目标”)

先看这两行代码:

clean:
            rm *.o temp
    正像我们前面例子中的“clean”一样,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
    当然,为了避免和文件重名的这种情况(make无法区分文件形式的目标和伪目标),我们可以使用 “.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”,还让make知道,不应该像处理一般规则那样,从源文件来建立以下以工作目标为名的文件。因此,make可以优化它的一般规则搜索程序以提高性能。
    .PHONY : clean
    只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
     .PHONY: clean
    clean:
            rm *.o temp
    伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
    all : prog1 prog2 prog3
    .PHONY : all
    prog1 : prog1.o utils.o
            cc -o prog1 prog1.o utils.o
    prog2 : prog2.o
            cc -o prog2 prog2.o
    prog3 : prog3.o sort.o utils.o
            cc -o prog3 prog3.o sort.o utils.o
    我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。
    随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:
    .PHONY: cleanall cleanobj cleandiff
    cleanall : cleanobj cleandiff
            rm program
    cleanobj :
            rm *.o
    cleandiff :
            rm *.diff
“make clean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。

clean没有依赖模块,因为没有时间标记可供比较,所以它总被执行;它的实际意图是引出后面的rm命令来删除某些目标文件。我们看到rm命令以-开头,这时即使表示make将忽略命令结果,所以即使没有目标供rm命令删除而返回错误时,make clean依然继续向下执行。

 

 

 摘自百度文库

posted @ 2015-06-07 18:06  在风的记忆里  阅读(166)  评论(0编辑  收藏  举报