1、GNU Make 中文手册 v3.8 学习 这个手册翻译整理: 徐海兵 , 先表示一些尊敬. “本人在工作之余,花了 18 个多月时间完成对“info make”的翻译整理,完成这个中文版手册”, 工夫不负有心人. 链接器将.o 文件中使用的函数和其它.o 或者库文件中的相关符号进行合并,对所有文件中的符号进行重新安排(重定位),并链接系统相关文件(程序启动文件等)最终生成可执行程序。 Linux 中,共享库文件格式通常为“ELF”格式。共享库已经具备了可执行条件。 模块中各个成员的地址(变量引用和函数调用)都是相对地址。使用此共享库的程序在运行时,共享库被动态加载到内存并和主程序在内存中进行
2、连接。多个可执行程序可共享库文件的代码段(多个程序可以共享的使用库中的某一个模块,共享代码,不共享数据)。另外共享库的成员对象可被执行(由 libdl.so 提供支持)。 在 make 读取 Makefile 以后会建立一个编译过程的描述数据库。此数据库中记录了所有各个文件之间的相互关系,以及它们的关系描述(要是我能“看“ 到这个数据库,就能使用程序trace,你知道一个大型的 project 的 makefile 有多复杂) 当使用 make 工具进行编译时,工程中以下几种文件在执行 make 时将会被编译(重新编译): 1. 所有的源文件没有被编译过,则对各个 C 源文件进行编译并进行链接
3、,生成最后的可执行程序; 2. 每一个在上次执行 make 之后修改过的 C 源代码文件在本次执行 make 时将会被重新编译; 3. 头文件在上一次执行 make 之后被修改。则所有包含此头文件的 C 源文件在本次执行make 时将会被重新编译。(这就是为什么头文件修改后,很多文件都会被重新 compile 的原因) 首先书写时,可以将一个较长行使用反斜线()来分解为多行,这样可以使我们的Makefile 书写清晰、容易阅读理解。但需要注意:反斜线之后不能有空格(这也是大家最容易犯的错误,错误比较隐蔽) 编译.c 源文件规则的命令可以不用明确给出。这是因为 make 本身存在一个默认的规则,
4、能够自动完成对.c 文件的编译并生成对应的.o 文件。它执行命令“cc -c”来编译.c 源文件。 对一个目标文件是“N.o”,倚赖文件是“N.c” 的规则,完全可以省略其规则的命令行,而由make 自身决定使用默认命令。此默认规则称为 make 的隐含规则。 书写规则建议的方式是:单目标,多依赖。就是说尽量要做到一个规则中只存在一个目标文件,可有多个依赖文件。尽量避免多目标,单依赖的方式。这样后期维护也会非常方便,而且Makefile 会更清晰、明了。 在这个 Makefile 中,根据依赖而不是目标对规则进行分组(Wrong!)。上例的 Makefile就可以这样来实现: #sample
5、Makefileobjects = 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 例子中头文件“defs.h”作为所有.o 文件的依赖文件。其它两个头文件作为其对应规则的目标中所列举的所有.o 文件的依赖文件。 但是这种风格的
6、 Makefile 并不值得我们借鉴。问题在于:同时把多个目标文件的依赖放在同一个规则中进行描述(一个规则中含有多个目标文件),这样导致规则定义不明了,比较混乱。建议大家不要在 Makefile 中采用这种方式了书写。否则后期维护将会是一件非常痛苦的事情。 “include”指示符告诉 make 暂停读取当前的 Makefile,而转去读取“include”指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。通常指示符“include”用在以下场合: 1. 有多个不同的程序,由不同目录下的几个独立的 Makefile 来描述其重建规则。它们需要使用一组通用的变量定义或者模式规
7、则。 2. 当根据源文件自动产生依赖文件时;我们可以将自动产生的依赖关系保存在另外一个文件中,主 Makefile 使用指示符“include”包含这些文件。这样的做法比直接在主 Makefile 中追加依赖文件的方法要明智的多。其它版本的 make 已经使用这种方式来处理。( 我想看看这方面的例子)当在这些目录下都没有找到“include”指定的文件时,make 将会提示一个包含文件未找到的告警提示,但是不会立刻退出。而是继续处理 Makefile 的后续内容。当完成读取整个 Makefile 后,make 将试图使用规则来创建通过指示符 “include”指定的但未找到的文件,当不能创建它
8、时(没有创建这个文件的规则),make 将提示致命错误并退出。 通常我们在 Makefile 中可使用“-include”来代替“include”,来忽略由于包含文件不存在或者无法创建时的错误提示(“-”的意思是告诉 make,忽略此操作的错误。make 继续执行)。 Makefile 中符号“$”有特殊的含义(表示变量或者函数的引用),在规则中需要使用符号“$”的地方,需要书写两个连续的( “$”)。 Maekfile 中表示文件名时可使用通配符。可使用的通配符有:“*”、“?”和“”。在 Makefile 中这些统配符并不是可以用在任何地方,Makefile 中统配符可以出现在以下两种场合
9、: 1. 可以用在规则的目标、依赖中,make 在读取 Makefile 时会自动对其进行匹配处理(通配符展开); 2. 可出现在规则的命令中 ,通配符的通配处理是在 shell 在执行此命令时完成的。 除这两种情况之外的其它上下文中,不能直接使用通配符。而是需要通过函数“wildcard”来实现。 比如变量定义中使用的通配符不会被统配处理(因此在变量定义中不能使用通配符,否则在某些情况下会出现非预期的结果,下一小节将会详细讨论)。在 Makefile 有这样一个变量定义:“objects = *.o”。它表示变量“objects”的值是字符串“*.o” (并不是期望的空格分开的 .o 文件列
10、表)。当需要变量“objects”代表所有.o 文件列表示,需要使用函数“wildcard” (objects = $(wildcar *.o))。在规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是: $(wildcard PATTERN.) 。 在 Makefile 中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。 一般我们可以使用“$(wildcard *.c)”来获取工作目录下的所有的.c 文件列表。复杂一些用法
11、;可以使用“$(patsubst %.c,%.o,$(wildcard *.c)”,首先使用“wildcard” 函数获取工作目录下的.c 文件列表;之后将列表中所有文件名的后缀.c 替换为.o 。这样我们就可以得到在当前目录可生成的.o 文件列表。因此在一个目录下可以使用如下内容的 Makefile 来将工作目录下的所有的.c 文件进行编译并最后连接成为一个可执行文件: #sample Makefile objects := $(patsubst %.c,%.o,$(wildcard *.c) foo : $(objects) cc -o foo $(objects) “自动化变量”,诸如“
12、$”等。规则命令行中的自动化变量“$”代表所有通过目录搜索得到的依赖文件的完整路径名(目录 + 一般文件名)列表。“$”代表规则的目标。所以对于一个规则我们可以进行如下的描述: foo.o : foo.c cc -c $(CFLAGS) $ -o $ 使用伪目标有两点原因:1. 避免在我们的 Makefile 中定义的只执行命令的目标(此目标的目的为了执行执行一些列命令,而不需要创建这个目标)和工作目录下的实际文件出现名字冲突。2. 提高执行 make 时的效率,特别是对于一个大型的工程来说,编译的效率也许你同样关心。如:在 make 的并行和递归执行过程中。此情况下一般会存在一个变量,定义为
13、所有需要 make 的子目录。对多个目录进行 make 的实现方式可以是:在一个规则的命令行中使用 shell 循环来完成。如下:SUBDIRS = foo bar baz subdirs: for dir in $(SUBDIRS); do $(MAKE) -C $dir; done 但这种实现方法存在以下几个问题:1. 当子目录执行 make 出现错误时,make 不会退出。就是说,在对某一个目录执行 make 失败以后,会继续对其他的目录进行 make。在最终执行失败的情况下,我们很难根据错误提示定位出具体是在那个目录下执行 make 时发生错误。这样给问题定位造成了很大的困难。为了解决
14、这个问题,可以在命令行部分加入错误监测,在命令执行错误后主动退出。不幸的是,如果在执行 make时使用了“-k” 选项,此方式将失效。2. 另外一个问题就是使用这种 shell 的循环方式时,没有用到 make 对目录的并行处理功能,由于规则的命令是一条完整的 shell 命令,不能被并行处理。 有了伪目标之后,我们可以用它来克服以上实现方式所存在的两个问题。 SUBDIRS = foo bar baz .PHONY: subdirs $(SUBDIRS) subdirs: $(SUBDIRS) $(SUBDIRS):$(MAKE) -C $foo: baz 上边的实现中有一个没有命令行的规则
15、“foo: baz”,此规则用来限制子目录的 make 顺序。它的作用是限制同步目录“foo” 和“baz” 的 make 过程(在处理“foo”目录之前,需要等待“baz”目录处理完成)。提醒大家:在书写一个并行执行 make 的 Makefile 时,目录的处理顺序是需要特别注意的。(在 broadcom 的 project 中,clean 的工作就是用了 SUBDIRS 类似的用法:SUBDIRS_APP = $(SUBDIRS_BROADCOM) $(SUBDIRS_OPENSOURCE) $(SUBDIRS_ALPHASOURCE)SUBDIRS = $(foreach dir, $
16、(SUBDIRS_APP), $(shell if -d “$(dir)“ ; then echo $(dir); fi)而后面 subdirs: $(patsubset %, _dir_%, $(SUBDIRS)$(patsubset %, _dir_%, $(SUBDIRS):$(MAKE) -C $(patsubset _dir_%, %, $) $(TGT) ) make 存在一个内嵌隐含变量 “RM”,它被定义为:“RM = rm f”。 静态模式规则是这样一个规则:规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。静态模式规则比多目标规则更通用,它不需要多
17、个目标具有相同的依赖。但是静态模式规则中的依赖文件必须是相类似的而不是完全相同的。首先,我们来看一下静态模式规则的基本语法: TARGETS .: TARGET-PATTERN: PREREQ-PATTERNS . COMMANDS首先在目标模式和依赖模式中,一般需要包含模式字符“%”。在目标模式(TAGET-PATTERN)中“%”可以匹配目标文件的任何部分,模式字符“%”匹配的部分就是“茎” 。目标文件和目标模式的其余部分必须精确的匹配。看一个例子:目标“foo.o”符合模式“%.o” ,其“茎” 为“foo” 。而目标“foo.c”和“foo.out”就不符合此目标模式。 每一个目标的依
18、赖文件是使用此目标的“茎”代替依赖模式(PREREQ-PATTERNS)中的模式字符“%”而得到。例如:上边的例子中依赖模式(PREREQ-PATTERNS)为“%.c”,那么使用“茎”“foo”替代依赖模式中的 “%”得到的依赖文件就是 “foo.c”。需要明确的一点是:在模式规则的依赖列表中使用不包含模式字符“%”也是合法的。代表这个文件是所有目标的依赖文件。 我们来看一个例子,它根据相应的.c 文件来编译生成“foo.o”和“bar.o”文件: objects = foo.o bar.o all: $(objects) $(objects): %.o: %.c $(CC) -c $(CF
19、LAGS) $ $当执行此规则的命令时,自动环变量“$*”被展开为“茎”。在这里就是 “big”和“little”。 典型的用法是在使用“echo”命令输出一些信息时。如:echo 开始编译 XXX 模块 .执行时,将会得到“开始编译 XXX 模块.”这条输出信息。如果在命令行之前没有字符“”,那么,make 的输出将是:echo 编译 XXX 模块 .编译 XXX 模块 . 另外,如果使用 make 的命令行参数“-n” 或“-just-print”,那么 make 执行时只显示所要执行的命令,但不会真正的去执行这些命令。只有在这种情况下 make 才会打印出所有 make需要执行的命令,其
20、中也包括了使用“” 字符开始的命令。这个选项对于我们调试 Makefile 非常有用,使用这个选项我们可以按执行顺序打印出 Makefile 中所有需要执行的所有命令。 命令的执行规则中,当目标需要被重建时。此规则所定义的命令将会被执行,如果是多行命令,那么每一行命令将在一个独立的子 shell 进程中被执行(就是说,每一行命令的执行是在一个独立的 shell进城中完成)。因此,多行命令之间的执行是相互独立的,相互之间不存在依赖(多条命令行的执行为多个相互独立的进程)。在 Makefile 中书写在同一行中的多个命令属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独立的 she
21、ll 命令行。因此:在一个规则的命令中,命令行 “cd”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完整的 shell 命令行。如:foo : bar/losecd bar; gobble lose ./foo 中断 make 的执行make 在执行命令时如果收到一个致命信号(终止 make),那么 make 将会删除此过程中已经重建的那些规则的目标文件。其依据是此目标文件的当前时间戳和 make 开始执行时此
22、文件的时间戳是否相同。删除这个目标文件的目的是为了确保下一次 make 时目标文件能够被正确重建。其原因我们上一节已经有所讨论。假设正在编译时键入“Ctrl-c”,此时编译器已经开始写文件“foo.o”,但是“Ctrl-c”产生的信号关闭了编译器。这种情况下文件“foo.o”可能是不完整的,但这个内容不完整的“foo.o”文件的时间戳比源程序foo.c的时间戳新。如果在 make 收到终止信号后不删除文件“foo.o”而直接退出,那么下次执行 make 时此文件被认为已是最新的而不会去重建它。最后在链接生成终极目标时由于某一个.o 文件的不完整,可能出现一堆令人难以理解的错误信息,或者产生了一
23、个不正确的终极目标。所以在重新 make 之前,一定要 make clean 一下. 上层 make 过程要将所执行的 Makefile 中的变量传递给子 make 过程,需要明确地指出。在 GNU make 中,实现此功能的指示符是“export” 。当一个变量使用“export”进行声明后,变量和它的值将被加入到当前工作的环境变量中,以后在 make 执行的所有规则的命令都可以使用这个变量。而当没有使用指示符“export” 对任何变量进行声明的情况下,上层 make 只将那些已经初始化的环境变量(在执行 make 之前已经存在的环境变量)和使用命令行指定的变量(如命令“make CFLA
24、GS +=-g”或者 “make e CFLAGS +=-g”)传递给子 make 程序, 存在两个特殊的变量“SHELL”和“MAKEFLAGS”,对于这两个变量除非使用指示符“unexport”对它们进行声明,它们在整个 make 的执行过程中始终被自动的传递给所有的子make。 当不希望将一个变量传递给子 make 时,可以使用指示符“unexport”来声明这个变量。格式如下:unexport VARIABLE . 一个不带任何参数的指示符“export” 指示符:export含义是将此 Makefile 中定义的所有变量传递给子 make 过程。需要说明的是:单独使用“export”
25、 来导出所有变量的行为是老版本 GNU make 所默认的。但是在新版本的 GNU make 中取消了这一默认的行为。因此在编写和老版本 GNU make 兼容的Makefile 时,需要使用特殊目标“.EXPORT_ALL_VARIABLES”来代替“export” , -w 选项在多级 make 的递归调用过程中,选项“-w” 或者“-print-directory”可以让 make 在开始编译一个目录之前和完成此目录的编译之后给出相应的提示信息,方便开发人员跟踪 make 的执行过程。例如,在目录“/u/gnu/make”目录下执行“make -w”,将会看到如下的一些信息:在开始执行之
26、前我们将看到:make: Entering directory /u/gnu/make.而在完成之后我们同样将会看到:make: Leaving directory /u/gnu/make.通常,选项“-w”会被自动打开。在主控 Makefile 中当如果使用“-C”参数来为 make 指定一个目录或者使用“cd”进入一个目录时,“-w”选项会被自动打开。主控 make 可以使用选项“-s”(“-slient”)来禁止此选项。另外,make 的命令行选项“-no-print-directory”,将禁止所有关于目录信息的打印。 变量名是大小写敏感的。变量“foo”、“Foo”和“FOO”指的是
27、三个不同的变量。Makefile 传统做法是变量名是全采用大写的方式。推荐的做法是在对于内部定义定义的一般变量(例如:目标文件列表 objects)使用小写方式,而对于一些参数列表(例如:编译选项 CFLAGS)采用大写方式 当我们定义了一个变量之后,就可以在 Makefile 的很多地方使用这个变量。变量的引用方式是:“$(VARIABLE_NAME)”或者“$ VARIABLE_NAME ”来引用一个变量的定义。 注意:Makefile 中在对一些简单变量的引用,我们也可以不使用“ ()”和“”来标记变量名,而直接使用“$x” 的格式来实现,此种用法仅限于变量名为单字符的情况。另外自动化变
28、量也使用这种格式。对于一般多字符变量的引用必须使用括号了标记,否则 make 将把变量名的首字母作为作为变量而不是整个字符串(“$PATH” 在 Makefile 中实际上是“$(P)ATH”)。这一点和 shell 中变量的引用方式不同。shell 中变量的引用可以是“$xx”或者“$xx”格式。但在 Makefile 中多字符变量名的引用只能是“$(xx)”或者“$xx”格式。 一般在我们书写 Makefile 时,各部分变量引用的格式我们建议如下:1. make 变量(Makefile 中定义的或者是 make 的环境变量)的引用使用“$(VAR)”格式,无论“VAR”是单字符变量名还是
29、多字符变量名。2. 出现在规则命令行中 shell 变量(一般为执行命令过程中的临时变量,它不属于 Makefile 变量,而是一个 shell 变量)引用使用 shell 的“$tmp”格式。3. 对 出现在命令行中的 make 变量我们同样使用“$(CMDVAR)” 格式来引用。例如:# sample MakefileSUBDIRS := src foo.PHONY : subdirSubdir :for dir in $(SUBDIRS); do $(MAKE) C $dir | exit 1; done 下边我们来看一个复杂一点的例子。分析一下直接展开式变量定义(:=)的用法,这里也用
30、到了 make 的 shell 函数和变量“MAKELEVEL”(此变量在 make 的递归调用时代表 make 的调用深度)。其中包括了对函数、条件表达式和系统变量“MAKELEVEL” 的使用:ifeq (0,$MAKELEVEL)cur-dir := $(shell pwd)whoami := $(shell whoami)host-type := $(shell arch)MAKE := $MAKE host-type=$host-type whoami=$whoamiendif第一行是一个条件判断,如果是顶层 Makefile,就定义下列变量。否则不定义任何变量。第二、三、四、五行分
31、别定义了一个变量,在进行变量定义时对引用到的其它变量和函数展开。最后结束定义。利用直接展开式的特点我们可以书写这样一个规则:$subdirs:$MAKE cur-dir=$cur-dir/$ -C $ all它实现了在不同子目录下变量“cur_dir”使用不同的值(为当前工作目录)。 在复杂的 Makefile 中,推荐使用直接展开式变量。因为这种风格变量的使用方式和大多数编程语言中的变量使用方式基本上相同。它可以使一个比较复杂的 Makefile 在一定程度上具有可预测性。而且这种变量允许我们利用之前所定义的值来重新定义它(比如使用某一个函数来对它以前的值进行处理并重新赋值),此方式在 Ma
32、kefile 中经常用到。尽量避免和减少递归式变量的使用。 第六章:Makefile 中的变量需要全部阅读! 通常我们会使用它来判断一个变量的值是否为空(不是任何字符)。参数值可能是通过引用变量或者函数得到的,因而在展开过程中可能造成参数值中包含空字符(空格等)。一般在这种情况时我们使用 make 的“strip” 函数来对它变量的值进行处理,去掉其中的空字符。格式为:ifeq ($(strip $(foo),)TEXT-IF-EMPTYendif 条件判断 ifeq, ifneq ifdef,ifndef对于“ifdef”需要说明的是:ifdef 只是测试一个变量是否有值,不会对变量进行替换
33、展开来判断变量的值是否为空。对于变量“VARIABLE-NAME”,除了“VARIABLE-NAME=”这种情况和从未定义该变量以外,使用其它方式对它的定义都会使“ifdef”返回真。就是说,即使我们通过其它方式(比如,定义它的值引用了其它的变量)给它赋了一个空值,“ifdef”也会返回真。 因此当我们需要判断一个变量的值是否为空的情况时,需要使用“ifeq”(或者“ifneq”)而不是“ifdef”。 make 的内嵌函数GNU make 函数的调用格式类似于变量的引用,以“$” 开始表示一个引用。语法格式如下:$(FUNCTION ARGUMENTS)或者:$FUNCTION ARGUME
34、NTS对于函数调用的格式有以下几点说明:调用语法格式中“FUNCTION”是需要调用的函数名,它应该是 make 内嵌的函数名。对于用户自己的函数需要通过 make 的“call”函数来间接调用。函数处理参数时,参数中如果存在对其它变量或者函数的引用,首先对这些引用进行展开得到参数的实际内容,而后才对它们进行处理。 以下是 GNU make 内嵌的文本(字符串)处理函数 $(subst FROM,TO,TEXT) $(patsubst PATTERN,REPLACEMENT,TEXT) $(strip STRINT)“strip”函数经常用在条件判断语句的表达式中,确保表达式比较的可靠和健壮!
35、 $(findstring FIND,IN) $(filter PATTERN,TEXT) $(filter-out PATTERN.,TEXT) $(word N,TEXT) $(wordlist S,E,TEXT) $(words TEXT) $(firstword NAMES) 文件名处理函数 $(suffix NAMES) $(basename NAMES) $(addsuffix SUFFIX,NAMES) $(addprefix PREFIX,NAMES) $(join LIST1,LIST2) $(wildcard PATTERN) $(foreach VAR,LIST,TEXT
36、) dirs := a b c dfiles := $(foreach dir,$(dirs),$(wildcard $(dir)/*)= files := $(wildcard a/* b/* c/* d/*) $(if CONDITION,THEN-PART,ELSE-PART) SUBDIR += $(if $(SRC_DIR) $(SRC_DIR),/home/src) eval 函数 shell 函数 函数“shell”所实现的功能和 shell 中的引用( )相同 函数的返回结果是此命令在 shell 中的执行结果。 make 仅仅对它的回返结果进行处理;make 将函数返回结果中
37、的所有换行符( “n”)或者一对“nr” 替换为单空格;并去掉末尾的回车符号(“n”)或者“nr”。 当引用“shell”函数的变量定义使用直接展开式定义时可以保证函数的展开是在 make 读入 Makefile 时完成。后续对此变量的引用就不会有展开过程。这样就可以避免规则命令行中的变量引用在命令行执行时展开的情况发生 内嵌变量“CFLAGS”代表了 gcc 编译器编译源文件的编译选项,我们就可以在 Makefile 中重新定义它,来改变编译源文件所要使用的参数。 # sample MakefileCUR_DIR = $(shell pwd)INCS := $(CUR_DIR)/includ
38、eCFLAGS := -Wall I$(INCS)EXEF := foo bar.PHONY : all cleanall : $(EXEF)foo : CFLAGS+=-O2bar : CFLAGS+=-gclean :$(RM) *.o *.d $(EXES)例子中没有出现任何关于源文件的描述。所有剩余工作全部交给了 make 去处理,它会自动寻找到相应规则并执行、最终完成目标文件的重建。隐含规则为我们提供了一个编译整个工程非常高效的手段,一个大的工程中毫无例外的会用到隐含规则。实际工作中,灵活运用 GNU make 所提供的隐含规则功能,可以大大提供效率。 模式规则类似于普通规则。只是在模式规则中,目标名中需要包含有模式字符“%”(一个),包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。例如:对于模式规则“%.o : %.c”,它表示的含义是:所有的.o 文件依赖于对应的.c 文件。我们可以使用模式规则来定义隐含规则。因此,一个模式规则的格式为: