快速掌握Makefile基础知识(全)

最近在看《跟我一起写 Makefile》作者:陈皓,学到很多,谢谢大神。

本文是为了自己使用方便,在原书的基础上做了一点提取和总结,详细内容请参考《跟我一起写 Makefile》,支持原创,感谢原著作者。

目录

makefile介绍

makefile总述

Makefile工作流程

makefile的显示规则

Makefile的隐含规则(自动推导)

Makefile书写要求

Makefile书写规则

1、书写规则--通配符

2、书写规则--文件搜索路径

3、书写规则--伪目标

4、书写规则--多目标

5、书写规则--静态模式

6、书写规则--自动生成依赖性

Makefile书写命令

Makefile变量

1、变量声明

2、引用变量

3、变量赋值

4、两种变量的高级使用方法

5、追加变量值

6、强制赋值(override 指示符)

7、多行变量

8、环境变量

9、目标变量

10、自动化变量

Makefile文件指示

1、Makefile文件名

2、引用另一个文件

3、条件判断

4、命令包

Makefile函数

1、字符串处理函数

2、文件名操作函数

3、foreach函数

4、if函数

5、call函数

6、origin 函数

7、shell函数

8、error和warning函数


makefile介绍

        makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。 makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。


makefile总述

Makefile 里主要包含了五个东西:

  • 显式规则:要生成的文件,文件的依赖文件,生成的命令。
  • 隐含规则:make 有自动推导的功能。
  • 变量定义:变量一般都是字符串。
  • 文件指示:其包括了三个部分

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

  • 注释:用“#”字符。

当然这其中也需要注意书写要求,函数引用等方面。


Makefile工作流程

在默认的方式下,也就是我们只输入 make 命令。那么

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

这就是整个 make 的依赖性,make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。


makefile的显示规则

target ... : prerequisites ...

command

………

………

target :就是一个目标文件,可以是 Object File,也可以是执行文件,还可以是一个标签(Label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。

prerequisites: 就是要生成那个target 所需要的文件或是目标,也就是常说的依赖文件。

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

这是一个文件的依赖关系,也就是说target 这一个或多个的目标文件依赖于prerequisites 中的文件,其生成规则定义在 command 中。

说白一点就是说,prerequisites中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。这就是 Makefile 的规则,也就是 Makefile 中最核心的内容。

举例:假如有6个c文件和2和h文件,那么Makefile会这样写,见下图:

执行:直接输入make,就会生成执行文件all和各种.o文件了

清除本次执行结果:make clean

说明:clean 不是一个文件,它只不过是一个动作名字,有点像 C 语言中的 lable 一样,其冒号后什么也没有,那么make 就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在 make 命令后明显得指出这个lable 的名字。(eg:make clean)

这样lable的方法非常有用,我们可以在一个 makefile 中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份等等。


Makefile的隐含规则(自动推导)

         GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为我们的 make 会自动识别并自己推导命令。
只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make找到一个 a.o,那么 a.c,就会是 a.o 的依赖文件,并且 cc -c a.c 也会被推导出来。

举例:在上一个文件的基础上,我们做两次自动推导。如下图所示:

注:“.PHONY”表示clean是个伪目标文件,make clean 就一定会执行。


Makefile书写要求

Makefile书写规则

1、书写规则--通配符

使用通配符make 支持“*”,“?”,“[...]” ,“~”通配符。

         “*.c”表示所以后缀为 c 的文件

“~/test”表示当前用户的$HOME 目录下的 test 目录。

如果想要这几个字符本身,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符

2、书写规则--文件搜索路径

搜索方法一:VPATH

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

         VPATH = src:../headers
                 上面的的定义指定两个目录,“src”和“../headers”,make 会按照这个顺序进行搜索。目录由“冒号”分隔。
       搜索方法二:使用 make 的“vpath”关键字
        a、vpath <pattern> <directories>

为符合模式<pattern>的文件指定搜索目录<directories>。

例1:

vpath %.h ../headers 

该语句表示,要求 make 在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)

例2:

vpath %.c foo:bar

vpath % blish

而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。

b、vpath <pattern>

清除符合模式<pattern>的文件的搜索目录。

c、vpath

清除所有已被设置好了的文件搜索目录。

3、书写规则--伪目标

target:是一个文件,通过make生成这个文件。

伪目标:不是一个文件,只是一个标签, make 无法生成它的依赖关系和决定它是否要执行。只有通过“make+伪目标”的方式才能生效。

例如:clean就是一个伪目标,执行方式:make clean

         

         为了避免和文件重名,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向 make 说明,不管是否有这个文件,都会执行 。如下

         

        伪目标也可以指定依赖。

        

4、书写规则--多目标

多个目标同时依赖于一个文件,并且其生成的命令大体类似,于是我们就能把其合并起来。

多个目标执行命令是同一个,这可能会因不用文件名的原因有点麻烦,我们可以使用一个自动化变量“$@”,这个变量表示着目前规则中所有的目标的集合,“$@”会依次取出目标,并执于命令。

例如:

bigoutput littleoutput : text.g

generate text.g -$(subst output,,$@) > $@

上述规则等价于:

bigoutput : text.g

generate text.g -big > bigoutput

littleoutput : text.g

generate text.g -little > littleoutput

其中-$(subst output,,$@)中的 subst是makefile的一个函数,具体后续再说,这里是截取字符串(big/little)的意思。

5、书写规则--静态模式

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活

语法:

<targets ...>: <target-pattern>: <prereq-patterns ...>

<commands>

....

targets :定义了一系列的目标文件,可以有通配符,是目标的一个集合。

target-parrtern :是指明了 targets 的模式,也就是的目标集模式。

prereq-parrterns :是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。

例如:

<target-parrtern>定义成“%.o”,意思是我们的<target>集合中都是以“.o”结尾的,而如果我们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”,那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。

知识点:$< 所有的依赖目标集(也是“a.c,b.c”),$@ 表示目标集(也就是“a.o b.o”)。

6、书写规则--自动生成依赖性

在 Makefile 中,我们的依赖关系可能会需要包含一系列的头文件,比如如果我们的 main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:main.o : main.c defs.h。但是如果是一个比较大型的工程,你必需清楚哪些 C 文件包含了哪些头文件,并且你在加入或删除头文件时也需要小心地修改 Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用 C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。

例如如果我们执行下面的命令:

cc -M main.c

其输出是:

main.o : main.c defs.h

需要提醒一句的是,如果你使用 GNU 的 C/C++编译器,你得用“-MM”参数,不然“-M”参数会把一些标准库的头文件也包含进来。

 


Makefile书写命令

1,每条命令的开头必须以[Tab]键开头,除非命令是紧跟在依赖规则后面的分号后的

2,“#”是注释符

3,@command,执行command内容,不会打印命令

4,上一条命令的结果应用在下一条命令时,使用分号分隔这两条命令,不要两条命令写在两行上 。

例如:

exec:

           cd /home/hchen; pwd

5,忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号“-”,标记为不管命令出不出错都认为是成功的

例如:

clean:

           -rm -f *.o

注意:make “-i”或是“--ignore-errors”,Makefile 中所有命令都会忽略错误

make  “-k”或是“--keep-going” ,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。

6,嵌套执行 make,每个目录中都书写一个该目录的 Makefile,有利于Makefile 变得更加地简洁

假如subdir下有一个Makefile,上层的Makefile可以这样写:

subsystem:

                    cd subdir && make

注意:传递变量到下级 Makefile 中,

语法:export <variable ...>    ,export不加参数,传递所有参数到下级Makefile

例如:export variable = value

有两个变量,一个是 SHELL,一个是 MAKEFLAGS,这两个变量不管你是否 export,其总是要传递到下层 Makefile 中。

7,定义命令包

Makefile 中多处使用一些相同命令序列,那么我们可以将这些相同的命令序列定义成一个变量。

语法:“define”开始,以“endef”结束

举例:


Makefile变量

1、变量声明

声明时需要给予初值。例如:foo = c

2、引用变量

变量名前加上“$”符号,但最好用小括号“()”或大括号“{}”把变量给包括起来,这样更安全。

变量名前加上“$”符号,但最好用小括号“()”或大括号“{}”把变量给包括起来,这样更安全。

3、变量赋值

1、“=”赋值:

说明:在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值

例如:

foo = $(bar)

ugh = Huh?

all:

           echo $(foo)         ==>执行结果:Huh

2、“:=”赋值

说明:使用“:=”操作符,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。

例如:

dir := /home/test

3、“?=”赋值

说明:判断变量是否被定义

FOO ?= bar

其含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做。

4、两种变量的高级使用方法

1、变量替换。

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

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

foo := a.o b.o c.o

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

2、变量嵌套

例如:

x = y

y = z

a := $($(x))

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

5、追加变量值

我们可以使用“+=”操作符给变量追加值 。

如:

objects = main.o foo.o bar.o utils.o

objects += another.o

于是$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)

注意:变量之前没有定义过,那么“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,或者前次的赋值符是“=”,那么“+=”也会以“=”来做为赋值。

6、强制赋值(override 指示符)

如果有变量是通过 make 的命令行参数设置的(make a=1),那么 Makefile 中对a这个变量的赋值会被忽略。如果你想在 Makefile 中设置这类参数的值,那么你可以使用“override”指示符。

语法:

override <variable> = <value>

override <variable> := <value>

override <variable> += <more text>

override define foo

          bar

endef

例如:如果在Makefile里定义:f =11,执行make f =22,执行结果打印f就是22,如果在Makefile里定义:override f =11,执行make f =22,执行结果打印f就是11。

注意:变量在定义时使用了“override”,则后续对它值进行追加时,也需要使用带有“override”指示符的追加方式。否则对此变量值的追加不会生效。

7、多行变量

还有一种设置变量值的方法是使用 define 关键字。使用 define 关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。

define two-lines

           echo foo

           echo $(bar)

endef

8、环境变量

make 运行时的系统环境变量可以在 make 开始运行时被载入到 Makefile 文件中,但是如果 Makefile 中已定义了这个变量,或是这个变量由 make 命令行带入,那么系统的环境变量的值将被覆盖。(如果 make 指定了“-e”参数,那么系统环境变量将覆盖 Makefile 中定义的变量)

9、目标变量

为某个目标设置的局部变量,它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效,而不会影响规则链以外的全局变量的值。

其语法是:

<target ...> : <variable-assignment>

<target ...> : overide <variable-assignment>            #针对于 make 命令行带入的变量,或是系统环境变量。

<variable-assignment>可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”

例如:prog : CFLAGS = -g

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

10、自动化变量

就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

       


Makefile文件指示

1、Makefile文件名

默认的情况下最好使用“Makefile”这个文件名;make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,然后进行解释。如果要指定特定的Makefile,比如:“Make.Linux”,“Make.Solaris”等,可以使用make的“-f” (--file)参数,如:make -f Make.Linux或make --file Make.AIX。

2、引用另一个文件

include <filename>

filename 可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符) 在 include前面可以有一些空字符,但是绝不能是[Tab]键开始。

例如:

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

make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。

有时候会看到sinclude和-include的使用,这里解释一下:

在上文书写命令第5条有提到“-”的使用,可以知道 “-include”代替“include”,是为了忽略由于包含文件不存在或者无法创建时的错误提示,让make继续执行。

为了和其它的make程序进行兼容,也可以使用“sinclude”来代替“-include”(GNU所支持的方式)。

3、条件判断

语法:

例1:

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

例2:

bar =

foo = $(bar)

ifdef foo

frobozz = yes

else

frobozz = no

endif

4、命令包

我们已经在前面书写命令的部分提到,这个不再赘述。


Makefile函数

函数的调用语法:$(<function> <arguments>)或是${<function> <arguments>}

1、字符串处理函数

2、文件名操作函数

3、foreach函数

语法:$(foreach <var>,<list>,<text>)

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

举例

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”。

4、if函数

语法:$(if <condition>,<then-part>)或是 $(if <condition>,<then-part>,<else-part>)

可见,if 函数 “else”部分可选。

<condition>参数是 if 的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算。

if 函数的返回值:<then-part>或者<else-part>的执行结果,如果不执行<then-part>并且没有<else-part>的情况,返回空。

5、call函数

makefile没有自定义函数的说法,但是有时候我们需要一些类似的使用,所以可以这样定义一个命令包,这个命令包中,可以定义许多参数,然后你可以用 call 函数来向这个命令包传递参数。call 函数是唯一一个可以用来创建新的参数化的函数。

其语法是:  $(call <expression>,<parm1>,<parm2>,<parm3>…)

当 make 执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次赋值。而<expression>的返回值就是 call 函数的返回值。

以前是这样使用,定义一个名字为t的命令包,直接使用$(t)即可调用,例如:

 

现在定义一个名字为test的命令包,包含$1,$2两个参数,现在就不能使用$(test)的方式调用了,只能使用call来完成,如图,执行之后$1 变成 hello,$2 变成world

当然你也可以在命令包里使用call的方式调用其他带有参数的命令包,如图:

6、origin 函数

origin :查看变量来源

语法: $(origin <variable>)

注意,<variable>是变量的名字,不是引用不要在<variable>中使用“$”字符。

返回值:

7、shell函数

shell 函数把执行操作系统命令后的输出作为函数返回,也就是我们可以使用shell来获取操作系统命令执行后的返回值。

例如:

执行结果打印1.txt的内容

8、error和warning函数

1、error语法:$(error <text ...>)

产生一个致命的错误,<text ...>是错误信息。注意error 函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。

示例一:

ifdef ERROR_001

$(error error is $(ERROR_001))

endif

示例二:

ERR = $(error found an error!)

.PHONY: err

err: ; $(ERR)

示例一会在变量 ERROR_001 定义了后执行时产生 error 调用,而示例二则在目录 err被执行时才发生 error 调用。

2、warning语法:$(warning <text ...>)

这个函数很像 error 函数,只是它并不会让 make 退出,只是输出一段警告信息,而 make 继续执行。

 

关于书中提到的make 的运行,make的参数,隐含规则,使用 make 更新函数库文件等这里不再讲述,有需要的请自行到《跟我一起写Makefile》或者其他书籍查阅,谢谢。

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以一起写 "makefile.pdf"。那么首先我们需要明确一下,"makefile.pdf" 是一个什么样的文件?它是一个关于 makefile 的说明文档,用于帮助初学者理解和编写 makefile。 接下来,我们需要确定 "makefile.pdf" 的结构和内容。该文档应该包括以下几个部分: 1. 简介:介绍 makefile 的定义、作用和使用场景。 2. 基本概念:解释 makefile 中的常用概念和关键词,例如目标、依赖、命令等。 3. 语法和格式:讲解 makefile 文件的基本语法和正确的格式。 4. 示例和实践:提供一些常见的 makefile 示例,包括编译 C 语言程序、处理源文件依赖关系等。 5. 高级特性:介绍一些进阶的 makefile 技巧,例如宏定义、条件判断、循环和自动化规则等。 6. 常见问题:列举并解答一些初学者可能遇到的问题,帮助读者更好地理解和应用 makefile。 最后,我们可以使用适合的文本编辑软件,如 Microsoft Word 或 LaTeX,来编写 "makefile.pdf"。在编写过程中,我们可以根据上述结构逐步填充内容,并注意使用清晰的语言和结构,以确保读者能够轻松理解和学习 makefile 的知识。 当完成文档编写后,我们可以保存为 PDF 格式,并可通过邮件、共享文档等方式与您共享 "makefile.pdf" 文件。通过阅读该文档,读者将能够快速入门和掌握 makefile基础知识和技巧,为编写高效的项目构建工具提供帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值