Makefile学习笔记

关于Makefile怎么写,参考http://blog.csdn.net/haoel/article/details/2886

一 关于编译和链接

    一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

    链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

    总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

二 Makefile的规则

三条:

    1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。     2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。     3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

    target … : prerequisites …             command             …             …

    target也就是一个目标文件,可以是Object File,也可以是执行文件,还可以是一个标签(Label).

    prerequisites就是,要生成那个target所需要的东西(文件或是目标)。

    command也就是make需要执行的命令。(任意的Shell命令)

    这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

For example:

    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

    在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个[Tab]键作为开头(在Makefile中的命令,必须要以[Tab]键开始)。make并不管命令是怎么工作的,它只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,make就会执行后续定义的命令。

    clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,如果其冒号后什么也没有,那么make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

三 Makefile里有什么

1 显示规则

2 隐晦规则

3 变量的定义

4 文件指示: 其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。

5 注释: Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。

四 Makefile的文件名

    默认情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了就解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,也可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,可以使用make的“-f”和“–file”参数,如:make -f Make.Linux或make –file Make.AIX。

五 引用其它的Makefile

include的语法是:

    include

filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

    在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。例如,有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

    include foo.make *.mk $(bar)

    等价于:

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

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

六 环境变量 MAKEFILES

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

    但是在这里还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当使用make时,所有的Makefile都会受到它的影响,这绝不是想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。

    当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明.

七 关于命令

    通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

    @echo 正在编译XXX模块……

当make执行时,会输出“正在编译XXX模块……”字串,但不会输出命令,如果没有“@”,那么,make将输出:

    echo 正在编译XXX模块……     正在编译XXX模块……

如果make执行时,带入make参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于调试Makefile,看看书写的命令执行起来是什么样子的或是什么顺序的,而make参数“-s”或“–slient”则是全面禁止命令的显示。

    如果要让上一条命令的结果应用在下一条命令时,应该使用分号分隔这两条命令。比如第一条命令是cd,希望第二条命令在cd之后的基础上运行,那么就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:

    示例一:         exec:                 cd /home/hchen                 pwd

    示例二:         exec:                 cd /home/hchen; pwd

当执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。

    每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。

    有些时候,命令的出错并不表示就是错误的。例如mkdir,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。之所以使用mkdir的意思就是一定要有这样的一个目录,只要这个目录存在了,就不希望mkdir出错而终止规则的运行。为了做到这一点,忽略命令的出错,可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:

   clean:             -rm -f *.o

还有一个全局的办法是,给make加上“-i”或是“–ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。还有一个要提一下的make的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。

    在一些大的工程中,会把不同模块或是不同功能的源文件放在不同的目录中,这种情况可以在每个目录中都书写一个该目录的Makefile,例如,有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么总控的Makefile可以这样书写:

    subsystem:             cd subdir && $(MAKE)

其等价于:

    subsystem:             $(MAKE) -C subdir

这两个例子的意思都是先进入“subdir”目录,然后执行make命令。总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

如果要传递变量到下级Makefile中,那么可以使用这样的声明:

    export

如果不想让某些变量传递到下级Makefile中,那么可以这样声明:

    unexport

如:

    示例一:

        export variable = value

        其等价于:

        variable = value         export variable

        其等价于:

        export variable := value

        其等价于:

        variable := value         export variable

    示例二:

        export variable += value

        其等价于:

        variable += value         export variable

如果要传递所有的变量,那么,只要一个export就行了,后面什么也不用跟,表示传递所有的变量。

八 变量

两种高级用法:

         我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

示例:

    foo := a.o b.o c.o     bar := $(foo:.o=.c)

这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有的“.o”字串“结尾”全部替换成“.c”,所以“$(bar)”的值就是“a.c b.c c.c”。

第二种高级用法是——“把变量的值再当成变量”。先看一个例子:

    x = y     y = z     a := $($(x))

在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)

我们还可以使用更多的层次:

    x = y     y = z     z = u     a := $($($(x)))

这里的$(a)的值是“u”.

    还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令.定义是以endef关键字结束,其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。命令需要以[Tab]键开头,define定义的命令也不例外.

下面的这个示例展示了define的用法:

define two-lines     echo foo     echo $(bar) endef

九 目标变量

    前面所有的在Makefile中定义的变量都是“全局变量”,在整个文件,都可以访问这些变量。当然,“自动化变量”除外,如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。

    当然,我们同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效,而不会影响规则链以外的全局变量的值。

其语法是:

    :

    : overide

可以是各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于make命令行带入的变量,或是系统环境变量。

这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:

    prog : CFLAGS = -g     prog : prog.o foo.o bar.o             $(CC) $(CFLAGS) prog.o foo.o bar.o

    prog.o : prog.c             $(CC) $(CFLAGS) prog.c

    foo.o : foo.c             $(CC) $(CFLAGS) foo.c

    bar.o : bar.c             $(CC) $(CFLAGS) bar.c

在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”.

十 条件判断语法

条件表达式的语法为:

        else         endif

其中表示条件关键字,如“ifeq”。这个关键字有四个。

第一个是我们前面所见过的“ifeq”

    ifeq (, )     ifeq ’’ ’’     ifeq "" ""     ifeq "" ’’     ifeq ’’ ""

比较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用make的函数。如:

    ifeq ($(strip $(foo)),)         endif

这个示例中使用了“strip”函数,如果这个函数的返回值是空(Empty),那么就生效。

第二个条件关键字是“ifneq”。

第三个条件关键字是“ifdef”。语法是:

    ifdef

如果变量的值非空,那到表达式为真。否则,表达式为假。当然,同样可以是一个函数的返回值。注意,ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子:

    示例一:     bar =     foo = $(bar)     ifdef foo     frobozz = yes     else     frobozz = no     endif

    示例二:     foo =     ifdef foo     frobozz = yes     else     frobozz = no     endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

第四个条件关键字是“ifndef”。

    在这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符“#”同样也是安全的。“else”和“endif”也一样,只要不是以[Tab]键开始就行了。

    特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

十一 foreach 函数     foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,它的语法是:

    $(foreach ,

<list>,<text>)这个函数的意思是,把参数

<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。&nbsp; &nbsp;所以,<var>最好是一个变量名,</var>

<list>可以是一个表达式,而<text>中一般会使用<var>这个参数来依次枚举
<list>中的单词。举个例子:&nbsp;&nbsp;&nbsp;&nbsp;names := a b c d

    files := $(foreach n,$(names),$(n).o) 

  $(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。 

十二 检查规则

有时候,我们不想让我们的makefile中的规则执行起来,只想检查一下命令,或是执行的序列。可以使用make命令的下述参数:

    “-n”     “–just-print”     “–dry-run”

------------------   

    “-t”     “–touch”     这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。


    “-W ”     这个参数需要指定一个文件。一般是是源文件(或依赖文件),make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。假定目标需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。

十三 make的其他参数

“-B” “–always-make” 认为所有的目标都需要更新(重编译)。

“-C

” “–directory=

” 指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make –C ~hchen/test –C prog”等价于“make –C ~hchen/test/prog”。

“-i” “–ignore-errors” 在执行时忽略所有的错误。

“-k” “–keep-going” 出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。 十四 隐含规则

    在使用Makefile时,有一些会经常使用,而且使用频率非常高的东西,比如,编译C/C++的源程序为中间目标文件(Unix下是[.o]文件,Windows下是[.obj]文件),这些就是早先约定了的,不需要我们再写出来的规则。

    “隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,即使Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,根本就不用写出来,make会自动推导出这种规则,并生成需要的[.o]文件。

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

1 使用隐含规则

    如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,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.c             cc –c foo.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的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。当然,我们也可以使用make的参数“-r”或“–no-builtin-rules”选项来取消所有的预设置的隐含规则。

1 编译C程序的隐含规则 “.o”的目标的依赖目标会自动推导为“.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”

2 编译C++程序的隐含规则 “.o”的目标的依赖目标会自动推导为“.cc”或是“.C”,并且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”作为C++源文件的后缀,而不是“.C”)

3 链接Object文件的隐含规则 “”目标依赖于“.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:“$(CC) $(LDFLAGS) .o $(LOADLIBES) $(LDLIBS)”。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:

    x : y.o z.o

并且“x.c”、“y.c”和“z.c”都存在时,隐含规则将执行如下命令:

    cc -c x.c -o x.o     cc -c y.c -o y.o     cc -c z.c -o z.o     cc x.o y.o z.o -o x     rm -f x.o     rm -f y.o     rm -f z.o

如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计