找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 4201|回复: 1

如何自己编写Makefile

[复制链接]
发表于 2012-2-6 14:33:58 | 显示全部楼层 |阅读模式
  相信很多朋友都有过这样的经历,看着开源项目中好几页的makefile文件,不知所云。在日常学习和工作中,也有意无意的去回避makefile,能改就不写,能用ide就用ide。其实makefile并没有想象的那么难写,只要你明白了其中的原理,自己实践几次。你也可以自己写makefile,让别人对你头来羡慕的目光。
  下面本人介绍一下自己的学习成果,初学阶段,欢迎大家多多指正。
  简单的说,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至可以在makefile中执行shell脚本。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  关于程序的编译和链接
  一般来说,无论是C还是C++,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile),一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。
  编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。
  链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
  下面我们开始看看如何自己写出makefile。
  Makefile的规则
  目标 : 需要的条件 (注意冒号两边有空格)

    命令  (注意前面用tab键开头)
  解释一下:
  1 目标可以是一个或多个,可以是Object File,也可以是执行文件,甚至可以是一个标签。
  2 需要的条件就是生成目标所需要的文件或目标
  3 命令就是生成目标所需要执行的脚本
  总结一下,就是说一条makefile规则规定了编译的依赖关系,也就是目标文件依赖于条件,生成规则用命令来描述。在编译时,如果需要的条件的文件比目标更新的话,就会执行生成命令来更新目标。
  下面举个简单的例子说明。如果一个工程有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文件中,然后执行make就可以进行编译,执行make clean就可以删除所有目标文件。解释一下,也就是说生成最终的目标文件edit,依赖于一系列的.o目标文件,而这些.o文件又是需要用源文件来编译生成的。
  需要注意的是,clean后面没有条件,而clean本身也不是文件,它只不过是一个动作名字,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。
  make是如何工作的
  在默认的方式下,也就是我们只输入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中使用变量
  前面的知识已经足以让你自己完成一个简单的makefile了,不过makefile的精妙之处远不止如此,下面来看看如何在makefile中使用变量吧。
  在上面的例子中,先让我们看看edit的规则:

      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


  我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果 makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
  于是,我们使用变量objects
  objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

  这样一来,原来的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 : 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)

  这样看起来方便多了吧,也更加省事了。如果有新的.o文件怎么办?当然是在objects里面添加了,这样只需要一处改变,很方便吧。
  让make自动推导
  GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的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

  clean :
            rm edit $(objects)

  当然,如果你觉得那么多[.o]和[.h]的依赖有点不爽的话,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。
  objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)


    $(objects) : defs.h
    kbd.o command.o files.o : command.h
    display.o insert.o search.o files.o : buffer.h
    clean :
            rm edit $(objects)

  不过话说回来,本人并不推荐这种方法。虽然简单,但是这种方法破坏了文件本身的依赖关系。如果文件过多的话,可能你自己都不清楚了。
  
  怎么样,makefile是不是既简单又强大?其实makefile远比这更强大,容我日后再慢慢介绍,今天就先到这里。
  如果大家觉得看我的文章不解渴,可以去这里看看http://blog.csdn.net/haoel/article/details/2887,大牛的文章,我个人是很喜欢的。
  最后,欢迎大家拍砖啊。

 楼主| 发表于 2012-2-6 14:34:35 | 显示全部楼层
如何自己编写Makefile(高级篇)
  上篇文章介绍了makefile的编写方法,相信大家有所收获。本文继续深入makefile,看看还有哪些高级用法可以大大提升我们的工作效率。本文是接着上篇博文——如何自己编写Makefile的,因此如果没有看过上篇文章的,建议先阅读一遍,以便更好地理解本文。
  在规则中使用通配符
  一个简单的例子
  clean:
         rm -f *.o

  表示删除所有.o文件,这很好理解。
   print: *.c
         lpr -p $?
         touch print

  表明print的生成依赖于所有.c文件,$?是一个自动化变量,以后会详细介绍。

  文件搜寻
  在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
  Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,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 %.c blish
    vpath %.c bar

  其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
    vpath %.c foo:bar
    vpath %.c blish

  而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
  
  伪目标
  最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,

    clean:
            rm *.o temp


  正像我们前面例子中的“clean”一样,即然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)

  因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于 “伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。

  当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向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 cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。
  makefile中的自动化变量 
  $@
  代表法定中的目标文件名。万一目标是一个文档(Linux中,等闲称.a文件为文档),那么它代表这个文档的文件名。在多目标的形式法定中,它代表的是哪个引发法定被厉行的目标文件名。
  $%
  法定的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,法定的目标是“foo.a(bar.o)”,那么,“$%”的值就为“bar.o”,“$@”的值为“foo.a”。万一目标不是函数库文件,其值为空。
  $<
  法定的第一个依靠文件名。万一是隐含法定,则它代表穿越目标指定的第一个依靠文件。
  $?
  所有比目标文件更新的依靠文件列表,空格瓜分。万一目标是静态库文件名,代表的是库成

员(.o文件)的更新情形。
  $^
  法定的所有依靠文件列表,利用空格分隔。万一目标是静态库文件名,它所代表的只能是所有库成员(.o文件)名。一个文件可重复的展目前目标的依靠中,变量“$^”只登记它的顺次引用情形。即便说变量“$^”会去掉重复的依靠文件。
  $+
  相仿“$^”,然而它保留了依靠文件中重复揭示的文件。重要用在过程链接时,库的穿插引用处所。

  更多的makefile知识
  学到这,我觉得写出一般的makefile是没有任何问题了。
  makefile博大精深,如果希望了解更多,请看大牛的文章http://blog.csdn.net/haoel/article/details/2890

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-4-30 13:37 , Processed in 0.012199 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表