CHAPTER 2 makefile介绍
2.1 makefile的规则
target ... : prerequisites ...
command
...
...
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
2.2 一个示例
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
2.3 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了。
2.4 makefile中使用变量
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)
2.5 让make自动推导
只要make看到一个.o 文件,它就会自动的把.c 文件加在依赖关系中,如果make找到一个whatever.o ,那么whatever.c ,就会是whatever.o 的依赖文件。并且cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。
main.o : main.c defs.h
cc -c main.c
可以化简成为:
main.o : defs.h
2.7 清空目标文件的规则
如何写clean,稳健的做法如下:
.PHONY : clean
clean :
-rm edit $(objects)
2.8 Makefile里有什么?
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
1. 显式规则。显式规则说明了,如何生成一个或多个目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2. 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写Makefile,这是由make所支持的。
3. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4. 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
5. 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用# 字符,这个就像C/C++中的// 一样。如果你要在你的Makefile中使用# 字符,可以用反斜框进行转义,如: #。
最后,还值得一提的是,在Makefile中的命令,必须要以Tab 键开始。
2.9 Makefile的文件名
最好使用Makefile
2.10 引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。
include <filename>; #filename可以是当前操作系统Shell的文件模式(可以包含路径和通配符)。
如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
1. 如果make执行时,有-I 或–include-dir 参数,那么make就会在这个参数所指定的目录下去寻找。
2. 如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。
如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:
-include <filename>;
2.11 环境变量MAKEFILES
不建议使用
2.12 make的工作方式
GNU的make工作时的执行步骤入下:(想来其它的make也是类似)
1. 读入所有的Makefile。
2. 读入被include的其它Makefile。
3. 初始化文件中的变量。
4. 推导隐晦规则,并分析所有规则。
5. 为所有的目标文件创建依赖关系链。
6. 根据依赖关系,决定哪些目标要重新生成。
7. 执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
CHAPTER 3 书写规则
规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。
3.1规则举例
foo.o: foo.c defs.h # foo模块
cc -c -g foo.c
看到这个例子,各位应该不是很陌生了,前面也已说过, foo.o 是我们的目标, foo.c 和defs.h 是目标所依赖的源文件,而只有一个命令cc -c -g foo.c (以Tab键开头)。这个规则告诉我们两件事:
1. 文件的依赖关系, foo.o 依赖于foo.c 和defs.h 的文件,如果foo.c 和defs.h 的文件日期要比foo.o文件日期要新,或是foo.o 不存在,那么依赖关系发生。
2. 生成或更新foo.o 文件,就是那个cc命令。它说明了如何生成foo.o 这个文件。(当然,foo.c文件include了defs.h文件)
3.2 规则的语法
targets : prerequisites
command
...
或者,
targets : prerequisites ; command
command
...
command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以Tab 键开头,如果和prerequisites在一行,那么可以用分号做为分隔。
prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
3.3 在规则中使用通配符
make支持三个通配符:* ,? 和~ 。这是和Unix的B-Shell是相同的。
波浪号( ~ )字符在文件名中也有比较特殊的用途。如果是~/test ,这就表示当前用户的$HOME目录下的test目录。而~hchen/test 则表示用户hchen的宿主目录下的test目录。
通配符代替了你一系列的文件,如.c 表示所有后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如: ,那么可以用转义字符\ ,如* 来表示真实的* 字符,而不是任意长度的字符串。
举个例子:
print: *.c
lpr -p $?
touch print
上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的.c 文件。其中的 $? 是一个自动化变量,我会在后面给你讲述。
objects = *.o
上面这个例子,表示了通配符同样可以用在变量中。并不是说.o 会展开,不!objects的值就是.o 。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有.o的文件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
另外举个例子:
- 列出一确定文件夹中的所有.c文件。
objects := $(wildcard *.c)
- 列出(1)中所有文件对应的.o文件,在(3)中我们可以看到它是由make自动编译出的。
$(patsubst %.c,%.o,$(wildcard *.c))
- 由(1)(2)两步,可写出编译并链接所有.c和.o文件
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这种用法由关键字“wildcard”,“patsubst”指出,关于Makefile的关键字,我们将在后面讨论。
3.4 文件搜寻
Makefile文件中的特殊变量VPATH 就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。
它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
vpath 为符合模式的文件指定搜索目录。
vpath 清除符合模式的文件的搜索目录。
vpath 清除所有已被设置好了的文件搜索目录。
vpath %.h ../headers # %代表通配零个或者若干个字符
举个例子:
vpath %.c foo
vpath % blish
vpath %.c bar
其表示.c结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
再举个例子:
vpath %.c foo:bar
vpath % blish
而上面的语句则表示.c 结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
3.5 伪目标
因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.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”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。.PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
3.6 多目标
Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个,这可能会可我们带来麻烦,不过好在我们可以使用一个自动化变量$@ (关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。
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„$@) 中的$ 表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思, $@ 表示目标的集合,就像一个数组,$@依次取出目标,并执于命令。
3.7 静态模式
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的定义成%.o ,意思是我们的;集合中都是以.o 结尾的,而如果我们的定义成%.c ,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的%(也就是去掉了.o这个结尾),并为其加上.c这个结尾,形成的新集合。
所以,我们的“目标模式”或是“依赖模式”中都应该有% 这个字符,如果你的文件名中有% 那么你可以使用反斜杠\进行转义,来标明真实的% 字符。
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明了我们的目标从 object中获取, object集合的模式,而依赖模式%.c则取模式%.o的%,也就是foo bar ,并为其加下.c 的后缀,于是,我们的依赖目标就是foo.c bar.c 。而命令中的 <和 <script type="math/tex" id="MathJax-Element-2">< 和</script>@ 则是自动化变量, <表示所有的依赖目标集(也就是foo.cbar.c), <script type="math/tex" id="MathJax-Element-3">< 表示所有的依赖目标集(也就是foo.c bar.c ),</script>@表示目标集(也就是“foo.o bar.o”)
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为“%.o”的内容。其它的内容,我就不用多说了吧。这个例子展示了Makefile中更大的弹性。
3.8 自动生成依赖性
没有看懂==
CHAPTER 4 书写命令
每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的开头必须以Tab 键开头,除非,命令是紧跟在依赖规则后面的分号后的。
我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被/bin/sh ——UNIX的标准Shell解释执行的。
4.1 显示命令
当我们用@ 字符在命令行前,
那么,这个命令将不被make显示出来,最具代表性的例子是,我们用echo这个功能来向屏幕显示一些信息
@echo 正在编译XXX模块...... #putput: 正在编译XXX模块......
echo 正在编译XXX模块...... #output: echo 正在编译XXX模块......
4.2 命令执行
需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。
exec:
cd /home/hchen
pwd #output : current directory
exec:
cd /home/hchen; pwd #output: /home/hchen
4.3 命令出错
为了忽略命令的出错,我们可以在Makefile的命令行前加一个减号- (在Tab键之后),标记为不管命令出不出错都认为是成功的。
clean:
-rm -f *.o
还有一个全局的办法是,给make加上-i 或是–ignore-errors 参数,那么,Makefile中所有命令都会忽略错误。
4.4 嵌套执行make
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
#以下两段等价:这两个例子的意思都是先进入“subdir”目录,然后执行make命令。
subsystem:
cd subdir && $(MAKE)
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。
传递变量到下级Makefile中: p21
4.5 定义命令包
如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以define开始,以endef结束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在define 和endef中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。
foo.c : foo.y
$(run-yacc)
我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包“run-yacc”中的 就是foo.y, @ 就是foo.c (有关这种以$ 开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。
CHAPTER 5 使用变量
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有: 、# 、= 或是空字符(空格、回车等)。
5.1 变量的基础
1.变量在声明时需要给予初值,
2.而在使用时,需要给在变量名前加上
符号,但最好用小括号()或是大括号把变量给包括起来。3.如果你要使用真实的
字符,那么你需要用$$ 来表示。
变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如:
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
展开后得到:
prog.o : prog.c
cc -c prog.c
当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个“替代”的原理。
5.2 变量中的变量
在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。
先看第一种方式(缺点是可以递归定义)
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
第二种方式:
x := foo
y := $(x) bar
x := later
#其等价于:
y := foo bar
x := later
值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。
上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和一个系统变量“MAKELEVEL”的使用:
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif
对于系统变量“MAKELEVEL”,其意思是,如果我们的make有一个嵌套执行的动作(参见前面的“嵌套使用make”),那么,这个变量会记录了我们的当前Makefile的调用层数。
下面再介绍两个定义变量时我们需要知道的:
1. 如何定义一个值为空格的变量
nullstring :=
space := $(nullstring) # end of the line
- 如果FOO没有被定义过,那么值就是bar
FOO ?= bar
等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
5.3 变量高级用法
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,我们先定义了一个 (foo)变量,而第二行的意思是把 (foo) 中所有以.o 字串“结尾”全部替换成.c,所以我们的$(bar)的值就是“a.c b.c c.c”。
另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有相同的模式,模式中必须包含一个% 字符,这个例子同样让$(bar)变量的值为“a.c b.c c.c”。
第二种高级用法是——“把变量的值再当成变量”。
x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))
这个例子中,$($($(z)))扩展为$($(y)),而其再次被扩展为$($(subst 1,2,$(x)))。$(x) 的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终, $(a) 的值就是$(variable2)的值——“Hello”。(喔,好不容易)
在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值:
first_second = Hello
a = first
b = second
all = $($a_$b) #$(all)的值就是“Hello”。
再来看看结合第一种技术的例子:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
这个例子中,如果
(a1)的值是“a”的话,那么,
(sources) 的值就是“a.c b.c c.c”;如果
(a1)的值是“1”,那么
(sources)的值是“1.c 2.c 3.c”。
再来看一个这种技术和“函数”与“条件语句”一同使用的例子:
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))
这个示例中,如果定义了“do_sort”,那么: foo :=\ $(sort a d b g q c) ,于是\$(foo) 的值就是“a b c d g q”,而如果没有定义“do_sort”,那么: foo := \$(strip a d b g q c),调用的就是strip函数。
当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。
5.4 追加变量值
我们可以使用+=操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
如果变量之前没有定义过,那么, += 会自动变成= ,如果前面有变量定义,那么+= 会继承于前次操作的赋值符。如果前一次的是:=,那么+=会以:=作为其赋值符,如:
variable := value
variable += more
等价于:
variable := value
variable := $(variable) more
但如果是这种情况:
variable = value
variable += more
由于前次的赋值符是= ,所以+= 也会以= 来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。
5.5 override 指示符
如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
override <variable>; = <value>;
override <variable>; := <value>;
当然,你还可以追加:
override <variable>; += <more text>;
对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用override指示符,如:
override define foo
bar
endef
5.6 多行变量(define 关键字)
define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef 关键字结束。
define two-lines
echo foo
echo $(bar)
endef
5.7 环境变量
make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)
当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。(参见前面章节)
5.8 目标变量
当然,我也同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。
<target ...> : <variable-assignment>;
<target ...> : overide <variable-assignment>
;可以是前面讲过的各种赋值表达式,如= 、:= 、+= ‘‘或是‘‘?= 。第二个语法是针对于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.ofoo.obar.o的规则), (CFLAGS)的值都是-g
5.9 模式变量
在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
我们知道,make的“模式”一般是至少含有一个% 的,所以,我们可以以如下方式给所有以.o结尾的目标定义目标变量:
%.o : CFLAGS = -O
语法格式:
<pattern ...>; : <variable-assignment>;
<pattern ...>; : override <variable-assignment>;
CHAPTER 6 使用条件判断
6.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
6.2 语法
<conditional-directive>
<text-if-true>
endif
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
的关键字: ifeq, ifneq, ifdef, ifndef
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif