您的位置:首页 > 其它

Makefile 学习整理

2016-04-07 21:05 316 查看
【说明】

开发人员在阅读或编写 Makefile时,可能会记不起某些知识点。

此文档目的是希望能起到提醒读者所遗忘的知识点的作用,故在整理时削弱了阅读流畅性,适用于对 Makefile有一定了解的开发人员。

【版权声明】

本文主体参考和整理自 陈皓 的文章《跟我一起写Makefile》。

【背景】

Makefile决定了整个工程的编译规则。会不会写 Makefile从一个方面说明了一个人是否具备完成大型工程的能力。

大多数 IDE都有 Makefile。比如 Delphi的 make,VisualC++的 nmake以及 Linux下GNU的 make。

【基本规则】

Makefile文件包含 2 个部分:依赖关系,生成目标文件的方法。

文件内容中的基本单元如下:

<目标文件> : <依赖文件> #依赖关系

[TAB]<命令> #生成目标文件的方法

或者写作:

<目标文件> : <依赖文件> ; <命令> #将依赖关系和生成目标的方法写成 1 行,<依赖文件>和<命令>之间使用分号隔开

当<依赖文件>中包含比<目标文件>更新的文件时,<命令>中的语句就会被执行。

<命令>必须以 TAB 键开始。

比如:

study : study.o

[TAB]gcc -o study study.o

study.o : study.c study.h

[TAB]gcc -c study.c

clean :

[TAB]rm -f *.o study

【解释器】

<命令>的语法和shell script 的语法相同,所以同样使用脚本解释器进行解释。

默认的解释器是 /bin/sh(UNIX的标准Shell)

【注释】

Makefile只允许行注释。使用符号 # 表示注释的开始。

【变量】

Makefile允许声明和使用变量,且变量名对大小写敏感。在对变量进行引用时使用小括号() 或花括号{} 将变量名括起来保证安全引用。

比如我们可以把上面步骤中的Makefile内容修改成下方这样:

studyNeeded = study.c study.h #声明变量

study : study.o

[TAB]gcc -o study study.o

study.o : $(studyNeeded) #引用变量

[TAB]gcc -c study.c

clean:

[TAB]rm -f *o study

采取这种写法,如果 study.o 的依赖文件有变化,我们只需要修改变量 studyNeeded 的内容就可以了。

特殊用法:

nullstring :=

space := $(nullstring) # 注释符号表明该行结束

上面这种写法将 space 定义为一个空格。$(nullstring)表明变量的值开始,#注释符表明变量定义终止。

dir := /foo/bar # 这种定义路径变量的方法包含了 4 个空格,用在 $(dir)/file 这样的情景中将导致意料之外的错误。

Foo ?= bar

这句变量声明的含义是,如果 Foo 没有被定义过,那么给变量 Foo 赋值为 bar,否则这条变量声明语句不执行。

变量嵌套:

x = y

y = z

z = u

a := $($($(x)))

上面 4 条语句执行完后,$(a)的值是u

变量拼接:

因为变量的值是字符串,所以多个变量的值可以直接拼接。

a = first

b = second

all = $($a_$b)

于是,$(all)的值就是first_second

追加变量值:

使用 += 操作符给变量追加值。如果被操作的变量之前没有定义,那么 += 操作符会进行定义。

objects = main.o foo.o

objects += new.o

这 2 条语句执行之后,$(objects)变成 main.o foo.o new.o

防止变量重写:

变量也可以用于存储编译时的参数选项,例如 CFLAGS = -c -g

有时会在运行 make 时传入命令行参数,为了使 Makefile 中的变量不会被同名的命令行参数替换,可以使用 override 关键字:

override VAR = VALUE

如果变量在定义时使用了 override 关键字,那么在向其追加值时也需要使用 override 关键字:

override VAR += NEWVALUE

目标变量:

var : CFLAGS = -g

所有涉及到生成 var 或由生成 var 衍生出的操作中,$(CFLAGS) 的值均为 -g。同名的环境变量也将被覆盖。

模式变量:

%.o : CFLAGS = -O

所有涉及到生成 .o 后缀文件或由生成 .o 后缀文件衍生出的操作中,$(CFLAGS) 的值均为 -O。同名的环境变量也将被覆盖。

自动化变量:

$@ 所有的目标文件

$^ 所有的依赖文件

$< 第一个依赖文件

$* 模式目标中符号% 之前的所有字符串,也被称为“茎”

【自动推导】

如果 make 找到一个 test.o 目标文件,那么相应的 test.c 依赖文件就会被自动推导出来,命令 gcc -c test.c 也会被自动推导出来。

所以形如下面这样的语句:

test : test.o

[TAB]gcc -o test test.o

test.o : test.c test.h

[TAB]gcc -c test.c

可以缩写为:

test.o : test.h

【引用其它Makefile】

在 Makefile 中使用关键字 include 可以在当前位置包含别的 Makefile 文件。关键字 include 与文件名、文件名与文件名之间使用空格隔开。

语法为:include <filename> <variables>

例如:include foo.nake *.mk $(bar)

环境变量 MAKEFILES 也有类似的作用。如果当前环境中定义了环境变量 MAKEFILES,那么 make 会把这个变量中的值做一个类似 include 的动作。这个变量中的值是其它的 Makefile,用空格分隔。

不推荐使用 MAKEFILES 环境变量,因为她会影响所有的 Makefile。

如果 Makefile 不能按照预期工作,可以检查一下是不是当前环境中定义了环境变量 MAKEFILES 在从中作梗。

【依赖文件的位置】

在工程比较大时,通常将源文件进行分类,存放在不同的目录中。

可以使用特殊变量 VPATH 保存源文件的存放路径。比如:

VPATH = src:../headers

上面的语句声明了 src 和 ../headers 两个目录,目录由冒号分隔。make会先在当前目录下搜索依赖文件,若没找到,再按照声明的目录顺序查找依赖文件。

还可以使用关键字 vpath 分别指定不同依赖文件的不同路径。比如:

vpath %.h ../headers 在 ../headers 中搜索所有文件名以 .h 结尾的依赖文件

vpath %.c ../src:src 依次在 ../src 和 src 中搜索所有文件名以 .c 结尾的依赖文件

vpath %.h 清除前一次设置的 .h 文件的搜索路径

vpath 清除所有已经设置的文件搜索路径

【伪目标】

伪目标既可作为目标文件,也可作为依赖文件。

使用关键字 .PHONY 声明伪目标。比如:

.PHONY : clean

clean :

[TAB]rm *.o temp

或者,作为目标文件:

all : prog1 prog2 #因为Makefile的第一个目标会被作为默认目标,所以应该将all写在第一行,其后再写.PHONY:all

.PHONY : all

prog1 : prog1.o utils.o

[TAB]gcc -o prog1 prog1.o utils.o

prog2 : prog2.o utils.o

[TAB]gcc -o prog2 prog2.o utils.o

或者,作为依赖文件:

.PHONY : cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff #伪目标既作目标文件,又作依赖文件

[TAB]rm program

cleanobj :

[TAB]rm *.o

cleandiff:

[TAB]rm *.diff

【静态模式】

静态模式可以让Makefile的功能更加灵活。格式如下:

<目标文件集合> : <目标模式集合> : <依赖模式集合>

[TAB]<命令>

举个例子:

objects = foo.o bar.o

all : $(objects)

$(objects) : %.o : %.c

$(CC) -c $(CFLAGS) $< -o $@

其中,$< 表示依赖模式集合,即foo.c bar.c,$@表示目标目标模式集合,即foo.o bar.o

等价于:

foo.o : foo.c

[TAB]$(CC) -c $(CFLAGS) foo.c -o foo.o

bar.o : bar.c

[TAB]$(CC) -c $(CFLAGS) bar.c -o bar.o

当我们的 %.o 有几百个时,这种 静态模式规则 的写法能极大提高效率。

另一个例子:

files = foo.elc bar.o lose.o

$(filter %.o, $(files)) : %.o : %.c

[TAB]$(CC) -c $(CFLAGS) $< -o $@

$(filter %.elc, $(files)) : %.elc : %.el

[TAB]emacs -f batch-byte-compile $<

其中,$(filter %.o, $(files)) 表示调用 Makefile的 filter函数,将 $(files)中模式为 %.o 的内容过滤出来。

【自动生成依赖关系】

如果 main.c 中存在语句 #include "defs.h",那么 main.o的依赖关系为 main.o : main.c defs.h

在 C文件中包含的头文件增加或减少时,如果手动修改 Makefile的依赖关系,工作将很繁琐,效率也极低。

在使用 gcc 进行编译时带上 -M 参数可以让编译器自动寻找 C文件所包含的头文件,并生成依赖关系。

以 main.c 为例,执行命令:

gcc -M main.c

这条命令的输出是:

main.o : main.c defs.h

如果使用的是GNU的C/C++编译器,需要使用参数 -MM ,否则 -M 参数会把一些标准库的头文件也包含进来。

【命令的执行】

如果要让上一条命令的结果应用在下一条命令时,应该把这 2 条命令写在一行上,并使用分号隔开,而不是写在两行上。

例如:

exec:

[TAB]cd /home/testUsr; pwd

使用 @ 作为命令的开头可以关闭命令回显,从而只打印命令执行的结果。

make会检测每条命令执行之后的返回码。如果命令执行出错,返回码将被置为非零值,make将会终止执行当前规则。

在[TAB]之后,紧接命令的位置添加符号 - 可以让 make忽略该条命令执行错误。

例如:

clean:

[TAB]-rm -f *.o

如果 rm 不带-前缀,当 .o文件不存在时命令 make clean将报错并停止;带 - 前缀的该命令则不会。

【模块化】

在大的工程中,不同功能的代码分属不同目录,每个目录都编写一个对应的特定 Makefile,再由一个总控 Makefile进行嵌套调用。

这有利于让我们的 Makefile更加简洁,更容易维护。

使用 export <variables ...> 命令将变量传递到下级 Makefile中;使用 unexport <variables ...> 命令防止将变量传递到下一级 Makefile中。

使用 make -w 参数让编译器在切换工作目录时打印出类似于“make: Entering directory '/home/testUsr/gnu/make'”的切换信息。

当使用 -C 参数指定 make 的下层 Makefile时, -w 参数会被自动打开。

【流程控制】

条件语句:

条件判断语句中允许多余的空格,但不能以[TAB]键开始。

make 在读取 Makefile时就计算条件表达式的值,而自动化变量(比如 $@、$^等)在运行时才出现,所以不要把自动化变量写在条件表达式里。

ifeq ($(CC), gcc) # 如果编译器是gcc

libs = $(libs_for_gcc) # 根据判断结果选择不同的库

else

libs = $(normal_libs)

endif

foo : $(objects)

$(CC) -o foo $(objects) $(libs)

ifneq ($(CC), gcc) # 如果编译器不是gcc

...

endif

ifdef VAR # 如果定义了VAR

...

endif

ifndef VAR # 如果没有定义VAR

...

endif

【make函数】

调用形式:$(<function> <arguments>)

函数调用以符号 $ 开头。函数名与参数间使用空格进行分隔。参数间使用逗号进行分隔。参数可以使用变量。

字符串处理函数:

$(subst <from>, <to>, <text>) 把<text>中的<from>字符串替换成<to>字符串。返回值为替换后的字符串

$(patsubst <pattern>, <replacement>, <text>) 将<text>中符合模式<pattern>的字符串使用<replacement>字符串替换。返回值是替换后的字符串。

$(strip <string>) 去掉<string>字符串中开头和结尾的空格。返回值是去掉首尾空格后的字符串。

$(findstring <find>, <in>) 在字符串<in>中查找<find>字符串。如果找到,返回<find>字符串,否则返回空字符串。

$(filter <pattern ...>, <text>) 将<text>中符合模式<pattern ...>的字符串过滤出来。返回值是过滤出的字符串。

$(filter-out <pattern ...>, <text>) 去除<text>中符合模式<pattern ...>的字符串。返回值是不符合<pattern ...>模式的字符串。

$(sort <list>) 将<list>中的数据以升序排序。返回值是排序后的字符串。

$(word <n>, <text>) 取出<text>中以空格作为分隔的第 n 个单词(从 1 开始计数),若 n 大于<text>中单词个数,则返回空字符串。

$(wordlist <s>, <e>, <text>) 取出<text>字符串中从第<s>个到第<e>个的单词,若<s>大于总单词个数,则返回空字符串,若<e>大于总单词个数,则返回从第<s>个到最后一个单词。

$(words <text>) 返回<text>中的单词个数。

$(firstword <text>) 返回<text>中的第 1 个单词。

文件名操作函数:

$(dir <filenames ...>) 返回<filenames ...>中所有文件各自所在的目录。

$(notdir <dirnames ...>) 返回<dirnames ...>中所有文件各自末尾的文件名。

$(suffix <filenames ...>) 返回<filenames ...>中所有文件各自的后缀。若没有后缀,则返回空字符串。

$(basename <filenames ...>) 返回<filenames ...>中所有文件各自的前缀。若没有前缀(比如隐藏文件),则返回空字符串。

$(addsuffix <suffix>, <filenames ...>) 给<filenames ...>中所有文件添加<suffix>后缀。

$(addprefix <prefix>, <filenames ...>) 给<filenames ...>中所有文件添加<prefix>前缀。

$(join <list1>, <list2>) 将<list2>中的各字符串添加到对应位置的<list1>中的各字符串后。比如 $(join aaa bbb, 111 222 333) 的返回值为 aaa111 bbb222 333

控制函数:

$(foreach <var>, <list>, <text>) 把参数<list>中的单词逐一取出放到<var>中,再执行<text>中的表达式。比如 $(foreach n, $(names), $(n).o) 将为 $(names)中的每个字符串添加 .o后缀,并将全部添加后缀后的单词作为一个整体字符串返回。

$(if <condition>, <then-part>, <else-part>) 作用即字面意思。

$(call <expression>, <param1>, <param2>, <param3> ...) 调用<expression>并使用<param1>、<param2>、<param3>依次取代<expression>中的第 1 个变量、第 2 个变量、第 3 个变量。

$(origin <variable>) 获取变量<variable>的性质。其返回值如下:

undefined 表示<variable>没有被定义

default 表示<variable>是一个默认变量

file 表示<variable>在 Makefile中被定义

command line 表示<variable>在命令行被定义

override 表示<variable>被override关键字定义

automatic 表示<variable>是一个自动化变量

environment 表示<variable>是一个环境变量

$(shell <command> <arguments>) 调用 shell命令

【GNU规范】

伪目标名称:

all 所有目标的目标,功能一般是编译所有的目标。

clean 删除所有被 make创建的文件。

install 安装已编译好的程序,即把可执行文件拷贝到指定的目录中。

print 列出改变过的文件。

tar 把源程序打包备份成一个 .tar文件。

dist 创建一个压缩文件。一般是将 .tar文件压缩成 .z文件或 .gz文件。

TAGS 更新所有的目标,以备完整的重编译使用。

check/test 测试 Makefile的执行流程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: