Makefile笔记A

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)

另外举个例子:

  1. 列出一确定文件夹中的所有.c文件。
objects := $(wildcard *.c)
  1. 列出(1)中所有文件对应的.o文件,在(3)中我们可以看到它是由make自动编译出的。
$(patsubst %.c,%.o,$(wildcard *.c))
  1. 由(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
  1. 如果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)progprog.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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值