当前位置: 首页 > 工具软件 > GNU Moe > 使用案例 >

Managing Projects with GNU make 学习笔记

都昊乾
2023-12-01
1. 简介
makefile定义了一种语言来描述源代码、中间文件及可执行文件之间的关系。
如果命令行指定了目标,则更新指定的目标,如果没有,则取第一个目标,也即默认目标。

1.1 目标与依赖

makefile包含构造程序的一组规则,规则包含三个部分:目标、依赖及执行命令。

	target: prereq1 prereq2
		commands

target是需要构造的东西,依赖则是在创建target前必须存在的东西。

commands为将依赖生成为target的shell命令。

1.2 依赖检查
对于-l<NAME>的依赖,make有特殊的支持,它表示依赖库文件。
make会查找libNAME.so,如果找不到,则查找libNAME.a。

1.3 减少重编
文件修改时,只会影响相关的编译,整个项目一般不会从头构造
1.4 调用make
make target 更新指定的target。如果目标是最新的,则什么也不做。
如果指定的target在makefile里不存在,也没有隐含规则,则提示无规则。
make有很多命令行参数,最有用的是 --just-print (或 -n)
它使make显示编译目标使用的命令,但不实际执行。
命令行的变量可以覆盖makefile里的变量。

1.5 makefile基本语法
更完整的形式(不是最完整的):

	target1 target2 target3: prerequisite1 prerequisite2
		command1
		command2
		command3

target可以有多个,依赖可以没有。如果没有依赖,则只有不存在的target会更新。
command必须以tab开始。make另启动子shell来执行command。
长的行可以用反斜杠分隔,依赖也可以使用。

2. 规则

规则有不同种类

 * 显式规则,明确指定目标和依赖
 * 模式规则,使用通配符
 * 隐式规则,makefile内置的模式规则或者扩展名规则

2.1 显式规则

大多数规则是显式规则。规则可以有多个目标,表示每个目标的依赖是相同的。

	target1 target2: prereq

等价于

	target1: prereq
	target2: prereq

规则不需要一次写全,make每次搜索目标文件时,会将目标和依赖加到依赖关系图中。
如果目标在依赖关系图中已存在,则将依赖添加到图中。

	vpath.o: vpath.c make.h config.h getopt.h gettext.h dep.h
	vpath.o: filedef.h hash.h job.h commands.h variable.h vpath.h

2.1.1 通配

make的通配符与bash一样:~, * , ?, [...], [^...]

*.*表示所有包含.的文件,?表示任意一个字符,[...]表示一组字符,选择相反的字符集用[^...]

~用于表示当前用户的home目录,~user表示那个用户user的home目录。

目标或是依赖里的通配由make处理,而命令里的通配由shell处理。
而make在读取makefile时会立即展开通配符。

2.1.2 Phony目标
不代表文件的目标即为phony目标,比如all和clean。
一般情况下,phony总会执行,因为相关的命令不产生目标名。

但是make无法区分文件目标和phony目标,假如一个phony目标恰好对应了一个文件名,make会把假的目标加到依赖图里。为了避免该问题,GNU make引入了一个特殊的目标:.PHONY来告诉make这个目标不是一个真实文件,可以用它来修饰任何一个伪目标,形式如下:

    .PHONY: clean
    clean:
        command

这样,即使存在一个叫clean的文件,make也会执行clean目标的命令。

将phony目标作为真实文件的依赖没有多大意义,因为phony总是旧的,这会导致目标文件重新生成。常用的做法是将phony作为目标。

按习惯,有一些标准的phony目标:

 * all:执行所有任务构建程序
 * install:安装程序
 * clean:删除从源码生成的二进制文件
 * distclean:删除不在源码发布范围内的所有产生文件
 * TAGS:创建tag表给编辑器使用
 * info:从Texinfo源码创建GNU info文件
 * check:运行与程序相关的测试

TAGS不是一个phony目标,因为ctags和etags的输出就是一个TAGS文件。

2.1.3 空目标
空目标与phony目标类似,phony target总是过时的,导至其依赖被重新生成。有时有些命令没有输出文件,希望只在有些情况下去执行,并且不想更新依赖,此时可以创建一个空文件的规则(也称为cookie):
prog: size prog.o
	$(CC) $(LDFLAGS) -o $@ $^
size: prog.o
	size $^
	touch size
在这例里,size规则用touch生成了一个文件名为size的空文件,这个空文件充当了一个时间戳的功能。只有在prog.o更新时,size规则才会执行。除非prog.o更新了,否则size规则不会导致prog被重编。也就是说这个规则在prog.o更新prog时执行,起到了特定条件下执行命令的目的。

空文件与自动变量$?结合时特别有用。$?表示比目标新的依赖集合。

    print: *.[hc]
        lpr $?
        touch $@

这个例子打印自上次执行print以来,变化过的文件。

通常,空文件是用来标记一个特定的时间。

2.2 变量
变量最简单的形式:

$(variable-name)


表示希望展开名为variable-name的变量。变量名需要用$()引用,但单字符变量可以省去括号。
makefile有自定义的一些变量。

2.2.1 自动变量
当规则匹配时,make会设置自动变量,它们提供了目标和依赖的清单,避免显式指定文件名,代码重复,且对通用的模式规则很重要。

有6个重要的自动变量:

$@
表示target的文件名

$%
the filename element of an archive member specification.
可能跟库有关系。

$<
第一个依赖的文件名

$?
比目标新的所有依赖

$^
所有依赖的文件名(会去掉重复项)

$+
与$^类似,但包含重复项

$*
目标文件名的根。根一般是不含后缀名的文件名。不建议在pattern rule之外使用。

自动变量只有在规则匹配时才会生成,因此只有在命令区才可以使用。

2.3 用VPATH和vpath查找文件

make只会在当前目录查找源文件

VPATH = src

告诉make,当前目录找不到文件时,到src里去找

VPATH变量包含了一个路径列表,make需要一个文件时就从这个列表里找。这个只对目标和依赖有效,命令区里的文件名是不用的。

VPATH有缺点:路径多的时候,效率会低。不同路径下有重名时,只会取第一个,vpath更好一些。

vpath pattern directory-list

前面的VPATH可以改写为:

vpath %.c src
vpath %.h include

vpath是否也存在重名的问题呢?

2.4 模式规则
make的内置规则都是模式规则,除了文件的根以%替代外,与普通规则无异(根是指后缀之前的部分)。可通过make --print-data-base查看make的内置规则。

可以修改内置规则的命令区。

2.4.1 模式
模式规则里,%基本上与unix shell的* 相同,代表任意数量的任意字符。%可以放在任意位置,但只能出现一次。
除%外的字符则用于匹配文件名。模式可以包含前缀、后缀和前后缀。
当make查找模式规则时,首先查找可以匹配的target。如果找到,在前缀和后缀之间的部分将作为名字的根。然后make查找对应的依赖,将根替换到依赖模式里。根至少包含一个字符。
模式可以只包含%,一般这种用法是生成一个可执行程序。

2.4.2 静态模式规则
静态模式规则是指只对特定目标生效的规则,如

$(OBJECTS): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

与普通的模式规则不同的只有前面的$(OBJECTS):限定,它指定该规则只对$(OBJECTS)变量列出的目标生效。

2.4.3 后缀规则
后缀规则是早期的(已经过时的)定义隐含规则的方式。其它版本的makefile不支持GNU make的模式规则,仍能在其它makefile里见到。

后缀规则包含一个或多个串接的后缀,用作目标:

.c.o:
	$(COMPILE.c) $(OUTPUT_OPTION) $<

这里的依赖在前,目标在后,它等价于:

%.o: %.c
	$(COMPILE.c) $(OUTPUT_OPTION) $<

将目标的后缀替换为依赖的后缀得到依赖,在有后缀名在已知清单里时,make才能识别。将后缀加入已知清单,通过特殊的目标.SUFFIXES实现,如:

.SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l

要删除所有已知后缀,不指定依赖即可:

.SUFFIXES:


2.5 隐含规则数据库
GNU make 3.80有约90条内置隐含规则。隐含规则是模式规则或者后缀规则。

2.5.1 使用隐含规则
当一个目标没有明确规则更新时,make会使用隐含规则。
当隐含规则不是所需要的时候,可以删除它们,比如:

%.o: %.l
%.c : %.l

没有命令区的模式从make的数据库中删除该规则。

由链式规则生成的文件称为中间文件,make对其处理不同。由于中间文件不出现在目标中(出现在目标中的不称为中间文件),make不会简单地更新中间文件,make创建中间文件是更新目标的一种副效应,因此make在退出前会删除中间文件。

2.5.2 规则结构
下面是一条内置规则:

%.o: %.c
	$(COMPILE.c) $(OUTPUT_OPTION) $<

定制这条规则可完全由它使用的一组变量来控制。COMPILE.c实质是一组变量的集合:

COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
CC = gcc
OUTPUT_OPTION = -o $@

改变CC变量可以更换C编译器,其它变量设置编译选项。

2.5.3 用于源码控制的隐含规则
make支持两种源码控制系统:RCS和SCCS。

2.5.4 一个简单的Help命令
没什么好说的

2.6 特殊目标
特殊目标是内置的phony目标,用来改变make的默认行为。比如.PHONY这个特殊目标,表示它的依赖不指向实际文件,并且总是过时的。除.PHONY外,还有其它的特殊目标。
一共有十二个特殊目标。

.INTERMEDIATE

该特殊目标的依赖是中间文件。如果make在更新其它目标时创建了这个文件,在make退出时会自动删除它。如果make在更新时文件已存在,则不会删除。
在定制链式规则时比较有用。

.SECONDARY
与INTERMEDIATE相同,但不会自动删除。

.PRECIOUS
当make在执行过种中被中断时,它可能会删除正在更新的目标文件,因为make不希望保留部分构建的文件。将文件标记为precious,make中断时不会删除它。
注意,当规则的命令产生错误时,make不会自动删除文件,这个只在被信号中断时出现。

.DELETE_ON_ERROR
与precious相反,当命令执行出错时,make会删除这些文件。

其它的特殊目标在涉及到的地方再谈论。

2.7 自动依赖生成
gcc有一个选项可以读取源文件并生成makefile依赖

gcc -M source.c

这个把戏就是用gcc -M遍历所有源文件,将结果写到一个依赖文件里,然后用include把它包含进来。

gcc -M 生成的结果一般需要修改一下,形成.o .d: 头文件清单这样的形式。

2.8 管理库
归档库,是一种特殊类型的包含其它文件的文件。库用来将相关的object文件组合到一起。

库可以有几种方式链到可执行文件中,最直接的方式是在命令行中指定,比如:

cc count_words.o libcounter.a /lib/libfl.a -o count_words

cc识别libcounter.a和/lib/libfl.a为库文件。

另一种方式是通过-l选项:

cc count_words.o -lcounter -lfl -o count_words

这个选项可以省略库文件名的前缀和后缀。-l使命令行更简单,而且不需要关心库的位置。cc从系统的标准库目录查找-l列出的库。而且,支持动态链接库的系统,链接器先查找共享库,找不到了再找静态库。
编译器的查找路径可以通过-L选项添加。

2.8.1 创建及更新库
没什么特别的

2.8.2 使用库作为依赖
库作为依赖时,可以用文件名,也可以用-l的方式。

对-l格式的库文件名解析模式存放在.LIBPATTERNS中,可以定制支持其它库文件名格式。

如果目标是一个库文件,则不能使用-l格式的依赖。因此对makefile内生成的库,必须指定文件名。

库的依赖关系很重要,库是有顺序的。如果出现循环依赖,则需要重复指定。然而,自动变量一般会丢掉重复项,此时,就需要用$+而不是$^了。

2.8.2 双冒号规则
过时的特性。

3. 变量和宏
变量名区分大小写。

要取用一个变量的值,用$()的形式。单符号变量可以省略()。

变量也可以用${}的形式,主要是早期的makefile用。

按惯例,代表常量的变量用大写,仅在makefile内使用的变量用小写,单词间以下划线分隔。用户定义的函数用小写,以破折号分隔。

3.1 变量有什么用
用变量来表示外部程序是个好主意。

3.2 变量类型
make有两类变量:简单扩展变量和递归扩展变量。

简单扩展变量由:=定义:

MAKE_DEPEND := $(CC) -M

称为简单变量是因为make在读到这一行后,右侧会被立即展开,右侧的所有变量引用都会展开,其展开结果为变量的值。假如上面的CC没有设置,那么上面的赋值成为<space>-M

变量没有定义不是一个错误。

第二种变量为递归扩展变量,使用=赋值:

MAKE_DEPEND = $(CC) -M

称其为递归扩展是因为右侧不会被求值。只有当该变量被使用时,扩展才会发生。

因为展开的滞后,赋值可以是乱序的,比如先定义MAKE_DEPEND,再定义CC。

递归变量每次被使用时,右侧都会重新求值。比如右侧包含date命令时,重新计算会保证每次使用都代表着执行时的值。

3.2.1 其它类型的赋值
除:=和=外,还有两种赋值类型。

?=是条件赋值,它只有在变量没有值的时候才会赋值。

OUTPUT_DIR ?= $(PROJECT_DIR)/out

这个功能与环境变量一起使用非常方便。

+=: 将文本添加到一个变量

3.3 宏
宏只是另一种方式的变量定义,它可以包含换行。

define 变量名
...
endef

命令行前面的@表示不回显命令,@用在宏前面,则对整个宏生效。

3.4 变量何时扩展

make运行有两个阶段。第一个阶段,make读入mkakefile及包含的makefile,此时,变量和规则加载到make的内部数据库,并生成依赖图。第二阶段,make分析依赖图来确定需要更新的目标,然后执行相应的命令来更新。

当make遇到一个递归变量或者define标志符时,它们的内容都原封不动地存下来。

当展开一个宏时,make会扫描展开的文本,并展开存在的变量引用和宏。如果宏是在action区间展开,则宏内的每一行前面会插入一个TAB。

总结如下:
 * 对于变量赋值,当make在第一阶段读取到这一行时,赋值左侧总是立即扩展(是指左侧的变量)。
 * =和?=的右侧在第二阶段使用时才会扩展。
 * :=的右侧立即展开。
 * 如果+=的左侧是一个简单变量,则右侧立即展开。否则推迟求值。
 * 对于宏定义,宏名立即展开,但宏体推迟到使用时展开。
 * 对于规则,目标和依赖总是立即展开,命令则推迟展开。
一个通用的规则是,在使用变量和宏之前定义他们。

3.5 目标/模式特定的变量

make提供了目标特定变量,也就是与一个目标绑定的变量定义,只有在处理这个目标及它的依赖时才有效。例如

gui.o: CPPFLAGS += -DUSE_NEW_MALLOC=1

只有在编译gui.o这个目标时,CPPFLAGS才包含-DUSE_NEW_MALLOC=1,在这个目标完成后,CPPFLAGS恢复到原始值。
目标特定变量的一般格式是:

target...: variable = value
target...: variable := value
target...: variable += value
target...: variable ?= value

变量在赋值前可以是不存在的。

3.6 变量从何处来
到目前为止,大多数变量是直接在makefile里定义的。但变量可以有下面一些来源:

文件: 变量定义在makefile或者文件,然后由makefile用include引入。

命令行: 变量可以在命令行定义。命令行的变量覆盖环境变量及makefile里的赋值。可以用override关键字来覆盖命令行的赋值。

环境变量: 当make启动时,环境变量的所有变量会自动定义为make变量,这些变量优先级很低,如果makefile或命令行参数指定了环境变量,会被覆盖。可以用--environment-overrides参数来产生相反的结果。
当递归执行make时,父make的一些变量会传给子make,默认情况下,只有环境变量会导出到子进程的环境,但可以用export将变量导出到环境。不带变量的export会导出所有变量。
可以用unexport阻止环境变量导出到子进程。

自动变量: 不用说了

传统上,环境变量用于区分不同的机器。

3.7 条件处理和包含处理
基本的条件处理形式如下:

    if-condition
        text if the condition is true
    endif

或者

    if-condition
        text if the condition is true
    else
        text if the condition is false
    endif

if-condition可以是下面的形式:

ifdef variable-name
ifndef variable-name
ifeq test
ifneq test

ifdef/ifndef测试中,variable-name不能用$()的形式。

test的形式可以为:"a" "b"    或者  (a,b)

单引双引均可。

条件处理可以用在宏定义里,命令脚本区域里。

ifeq/ifneq测试入参是否相等,白字符需要注意,当使用()形式的测试时,只有逗号后面的白字符是忽略的,但其它白字符都是有效的。

    ifeq (a, a)
     # these are equal
    endif
    ifeq ( b, b )
     # these are not equal  ' b' != 'b '
    endif

有时,变量扩展后会出现不想要的白字符,影响条件判断,可以用strip函数处理。

    ifeq "$(strip $(OPTIONS)) "-d"
      COMPILATION_FLAGS += -DDEBUG
    endif

3.7.1 include关键字
一个makefile可以包含其它文件,用include关键字,入参可以是任意数量的文件,通配符,也支持make变量。

3.7.2 include及依赖
当make遇到include时,展开通配符及变量引用,然后读入包含的文件,如果文件存在,则正常继续,如果不存在,make会报告,并继续读取剩下的makefile。
在所有文件读完后,make查找规则数据库,看是否有规则来更新包含文件(前面缺失的文件)。如果找到更新规则,则执行与普通的目标更新一样的过程。

如果include的文件是有规则来更新的,make会清空内部的数据库,并重读整个makefile(使用include的这个makefile)。如果在读取,更新,再读取之后,仍有include找不到文件,make报错并结束。

make把makefile也当作一个可能的目标,在整个makefile读入后,make会查找是否有规则来更新当前执行的makefile,如果有的话则处理这个规则,并检查makefile是否被更新,如果更新了,则会清空内部状态,并重新读取这个makefile。

make从哪里寻找include文件呢,绝对路径和相对路径。如果找不到,则查找--include-dir命令行参数指定的路径,然后查找编译内置的一些路径。
如果想让make忽略找不到的include文件,可以在include前面加减号(sinclude是-include的一个兼容方法)。

3.8 标准make变量

MAKE_VERSION     make版本号
CURDIR           当前工作目录
MAKEFILE_LIST    make已经读取的的makefile的清单
MAKECMDGOALS     当前make进程命令行指定的所有目标
.VARIABLES       当前已在makefile里定义的变量名的列表(除了目标特定变量),该变量只读。

4. 函数
函数引用就像变量引用一样,但包括一个或多个由逗号分隔的参数。

4.1 用户定义的函数
函数其实就是带参数的宏。宏体内使用$1, $2等来引用参数。

展开参数或宏的语法为:

$(call macro-name[, param1 …])

call是一个内置的make函数,将参数替换成$1, $2等。macro-name是宏或者变量的名字(宏只是允许换行的变量)

如果宏内引用了$n,而入参不够,则为空,如果入参多于引用,则多余部分不在宏内扩展。

4.2 内置函数
内置函数分为几类:字符串处理,文件名处理,流程控制,用户定义函数及其它一些函数。

函数的形式如下:

$(function-name arg1[,argn])

第一个入参的白字符会去掉,但后续参数前的白字符会保留(所以要注意空格问题)
有许多函数也支持模式作为入参。

4.2.1 字符串函数

$(filter pattern …,text)
filter返回text中与pattern匹配的内容。text是一个以空白符分隔的单词序列。

$(filter-out pattern …,text)
与filter作用相反,选择text中不匹配pattern的内容。

$(findstring string,text)
在text中查找string,如果找到,则返回string(是string,不是包含string的字符串),否则返回空。string不接受通配符。

$(subst search-string,replace-string,text)
简单的非通配的查找与替换。通常是用它来将某种后缀替换成另一种。比如:

sources := count_words.c counter.c lexer.c
objects := $(subst .c, .o, $(sources))

subst并不知道文件名及后缀,只是简单的字符串替换,因此如果文件名里有.c的话,也会被替换掉。

$(patsubst search-pattern,replace-pattern,text)
通配版本的查找与替换。pattern可以包含一个%。
另一种可移植的替换形式是:

$(variable:search=replace)

这种形式,原变量会被修改吗?会

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

$(word n,text)
返回text的第n个单词,从1开始编号。如果n大于单词数,则结果为空。例,取得列表的最后一项:

current := $(word $(words $(MAKEFILE_LIST)), $(MAKEFILE_LIST))

$(firstword text)
返回text的第一个单词,等价于$(word 1,text)

$(wordlist start,end,text)
返回text从start到end的单词(含边界)

4.2.2 重要的辅助函数

$(sort list)
排序,并去掉重复项。词典序。

$(shell command)
将命令传给子shell进程执行,将命令的标准输出读回并作为函数的返回值。不返回标准错误输出,也不返回程序退出状态。

4.2.3 文件名函数

($wildcard pattern …)
wildcard接受一组模式并展开,如果模式找不到匹配文件,则返回空字符串。
模式里可以用shell的通配符:~, * , ?, […], [^...]

$(dir list …)
返回list每个单词的目录部分。

$(notdir name …)
返回文件路径的文件名部分。

$(suffix name …)
返回参数中每个单词的后缀。常见用法是与findstring结合做条件处理。

$(basename name …)
返回文件名的文件名部分,仅去掉后缀,路径部分不变。

$(addsuffix suffix,name …)
给name里的每个单词附加后缀。

$(addprefix prefix,name …)
给name里的每个单词附加前缀。

$(join prefix-list,suffix-list)
是dir和notdir的补运算。join接受两个list,并将prefix-list的元素与suffix-list的元素串接,一一对应,可以用来重组被dir与notdir分开的部分。

4.2.4 流程控制

$(if condition,then-part,else-part)
根据condition的情况,展开第一个或第二个宏。
condition只要包含字符(包括空格),就为真,此时执行then-part,否则执行else-part。

$(error text)
打印严重错误信息,make终止。

$(foreach variable,list,body)
遍历处理

4.2.5 次常用函数

$(strip text)
移除text的前后白字符,并将内部所有白字符替换为一个空格。

$(origin variable)
返回一个描述变量的来源的字符串。有下面一些来源:

undefined  变量没有定义过
default  来自make的内置数据库,如果内置变量被覆盖,则返回最近一次的定义
environment  来自环境,且--environment-overrides未打开
environment override 来自环境,且--environment-overrides已打开
file  来自makefile
command line  来自命令行
override  来自override关键字
automatic  自动变量


$(warning text)
类似error,但make不会退出。可以用在任意地方,不需要放在规则里,有效的调试方法。

4.3 高级用户定义函数

call一个函数时,其入参转化为$1, $2等,而函数名则可通过$0得到。
可以利用它来打印函数调用情况。例子见书。

5. 命令

5.1 命令解析

命令以TAB起始,下面的错误表示make在target上下文之外遇到了命令:

makefile:20: *** commands commence before first target.  Stop.

当make parser在合法的上下文中看到一个命令时,切换到命令解析模式,每次构造一行脚本,命令脚本里可以出现的有:
 * 以TAB开头的命令,开辟子shell执行。包括ifdef, 注释, include等,在命令解析模式下都当作命令。
 * 忽略空行,不会传给子shell
 * 忽略以#开头的行(包括空格,但不是TAB)
 * 条件处理关键字,如ifdef, ifeq可以识别,并在脚本区执行。

5.1.1 长命令接续

用 \ 接续长命令。shell上需要加分号的要加分号分隔。

5.1.2 命令修辞

@
命令不回显

QUIET = @
hairy_script:
	$(QUIET) complex script

这种方式比较适合调试。

- (减号)
忽略出错命令

+
执行命令(不管--just-print是否指定)
用在递归makefile中。

5.1.3 错误和中断

make执行的每条命令都返回一个状态码,0表示命令成功,非0表示某种错误。一般程序返回非0值时,make停止,如果想让make忽略继续,可以用--keep-going (-k) 选项。减号也可以达到这个目的,但不建议使用,因为它会使自动错误处理更复杂。

5.1.3.1 删除和保留目标文件

因为历史原因,make在未能更新目标时,这个目标仍会保留,因为它的时间戳已经变了,是后续命令未能形成完整的数据。如果希望不完整的目标文件,可以将其作为.DELETE_ON_ERROR的依赖。如果.DELETE_ON_ERROR没有依赖,则所有目标文件的都会在出错时删除。

另一个问题就是在Ctrl+C时,make会中断,如果当前的目标文件被修改,make会将其删除。有时需要保留,用.PRECIOUS。

5.2 使用哪个SHELL

make需要传递命令给子shell执行时,使用/bin/sh。可以通过make变量SHELL改变。

5.3 空命令
空命令什么也不做:

header.h: ;


5.4 命令环境

make执行的命令继续了make自己的环境,包括当前工作目录,文件描述符及make传递的环境变量。创建subshell时,make还向环境添加了几个变量:

MAKEFLAGS
包含了传给make的命令行配置。

MFLAGS
与MAKEFLAGS一样,历史原因保留。

MAKELEVEL
make的嵌套层数。

5.5 命令求值

命令直到执行的时候求值,但ifdef前导符则在使用点立即求值。
当要执行一个命令脚本时,make先扫描脚本查找需要展开和求值的make部件。

5.6 命令行限制
不同操作系统下,对命令行长度的限制不同。
解决办法就是分解。

 类似资料: