📄 gmake.txt
字号:
以下例子从相应的".c"文件编译"foo.o"和"bar.o":
objects = foo.o bar.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
每个目标必须匹配目标模式,对于不匹配的目标会给出警告。如果列表中只有部分文件匹配模式,可以使用filter 函数移去不匹配的文件名:
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))" 结果是"bar.o lose.o"; "$(filter %.elc,$(files))" 的结果是"foo.elc"。
以下例子说明"$*"的使用:
bigoutput littleoutput : %output : text.g
generate text.g -$* > $@
命令 "generate"执行时,"$*"扩展为词干"big"或"little"。
3.11.2 静态模式规则和隐式规则
静态模式规则和隐式规则在作为模式规则是具有很多共同点,都有目标模式和构造依赖文件名的模式,不同之处在于make 决定何时应用规则的方法。隐式规则可应用于匹配其模式的任何目标,但只限于没有指定命令的目标,如果有多条可应用的隐式规则,只有一条被使用,取决于规则的顺序。反之,静态模式规则适用于规则中明确目标列表,不适用于其它目标且总是适用于指定的每个目标。如果有两条冲突的规则,且都有命令,这是一个错误。
静态模式规则比隐式规则优越之处如下:
★可为一些不能按句法分类,但可以显式列出的文件重载隐式规则
★不能判定目录中的精确内容,一些无关的文件可能导致make 适用错误的隐式规则;最终结果可能依赖于隐式规则的次序。适用静态模式规则时,这种不确定性是不存在的:规则适用于明确指定的目标。
3.12 双冒号规则
双冒号规则(Double-colon rules)的目标后是"::"而不是":",当一个目标出现在多条规则中时,其处理和普通规则的处理不同。当一个目标出现在多条规则中时,所有规则必须是相同类型的:都是普通的或者都是双冒号的。如果是双冒号,规则之间相互独立;如果目标需要更新,则规则的命令被执行;结果可能是没有执行,或者执行了其中一些,或者所有的规则都执行了。
同一目标的双冒号规则事实是完全孤立的,每条规则被被单独处理,就象不同目标的规则一样;规则按照在makefile 中出现的次序被处理,此类规则真正有意义的是那些于命令执行次序无关的。
这种规则有时比较晦涩不是特别有用;它提供了一种机制:通过不同依赖文件的更新来对目标进行不同的处理,这种情形很罕见。每个这种规则应当提供命令,如果没有,适用的隐式规则将使用。
3.13 自动生成依赖关系
在makefile 中,许多规则都是一些目标文件依赖于一些头文件。例如:"main.c"通过"#include"使用"defs.h",这样规则:
main.o: defs.h
告诉make 在"defs.h"变化时更新"main.o"。在程序比较大时,需要写许多这样的规则;而且当每次增删"#include"时,必须小心的更新makefile。许多现代的编译器可以帮你写这些规则,通常这是通过编译器的"-M"选项,例如命令:
cc -M main.c
输出以下内容:
main.o : main.c defs.h
这样就不必写这些规则,有编译器代劳了。
注意这样的依赖关系中提及"main.o",不会被隐式规则认为是中间文件,这意味这make 在使用过它之后不会将其删除。使用老的"make"程序时,习惯做法是使用"make depend"命令利用编译器的功能产生依赖关系,该命令会产生一个"depend"文件包含所有自动产生的依赖关系,然后在makefile 中使用"include"将其读入。
使用GNU 的make 时,重新生成makefile 的功能使得这种做法变得过时:从不需要显式请求更新依赖关系,因为它总是重新生成任何过时的makefile。
自动依赖关系生成推荐的做法是对每个源文件做一个makefile。对每个源文件"NAME.c",有一个makefile "NAME.d",其中列出了目标文件"NAME.o"依赖的所有文件,这样在源文件更新时,需要扫描来产生新的依赖关系。例子是一个从"NAME.c"产生依赖关系文件"NAME.d"的模式规则:
%.d: %.c
$(SHELL) -ec '$(CC) -M $(CPPFLAGS) $< | sed '\''s/\($*\)\.o[ :]*/\1 $@/g'\'' > $@'
-e 选项是当$(CC)命令失败时(exit 状态非0),shell 立刻退出。通常shell 的返回值是管道中最后一条命令(sed)的返回值,这样make 不会注意到编译器出错。
使用GNU 的C 编译器时(gcc),可以用"-MM"选项来代替"-M"选项,这样省略系统头文件的依赖关系。"sed"命令的目的是将main.o : main.c defs.h转换为main.o main.d : main.c defs.h
这样使得每个".d"文件依赖于".o"文件相应源文件和头文件,make 则可以在原文间或头文件变化时更新依赖关系文件。
如果定义了生成".d"文件的规则,可以使用"include"指令来读入所有的文件:
sources = foo.c bar.c
include $(sources:.c=.d)
例中使用替换变量来将源文件列表" foo.c bar.c"转换为依赖关系文件的列表。因为".d"文件和其它文件一样,不需要更多工作,make 会在需要时重新生成它们。
4 编写命令
规则的命令是由一一执行的shell 命令组成。除了以分号隔开写在依赖关系后的命令,每个命令行必须以tab 字符开始空行和注释行可以出现在命令行中,处理时被忽略(注意:以tab 字符开始的空行不是"空"行,是一条空命令)。可以在命令中使用任何程序,但这些程序是由$(SHELL)来执行的。
4.1 回显
通常make 打印出要执行的命令,称之为回显,这和亲自敲命令的现象是一样的。当行之前有"@"字符时,命令不再回显,字符"@"在传递给shell 前丢弃。
典型的用法是只对打印命令有效,比如"echo"命令:
@echo About to make distribution files
当make 使用"-n"或"-just-print"选项时,显示要发生的一切,但不执行命令。只有在这种情况下,即使命令以"@"开始,命令行仍然显示出来。这个选项对查看make 实际要执行的动作很有用。
‘-s"或"-silent"选项阻止make 所有回显,就象所有命令以"@"开始一样;一条没有依赖关系的".SILENT"规则有相同的作用,但是"@"更加灵活。
4.2 执行
在需要执行命令更新目标时,make 为每一行创建一个子shell 来执行。这意味着诸如为进程设置局部变量的shell 命令"cd"(改变进程的当前目录)不会影响以后的命令。如果需要"cd"影响下一个命令,将它们放在一行上用分号隔开,
这样make 认为是一条命令传递给shell 程序(注意:这需要shell 支持):
foo : bar/lose
cd bar; gobble lose > ../foo
另一个形式使用续行符:
foo : bar/lose
cd bar; \
gobble lose > ../foo
shell 程序的名字是通过"SHELL"变量来取得的。
"SHELL"变量不是通过环境来设置的(即需要在makefile 中设置),因为"SHELL"环境是个人选择的,如果不同人的选择会影响makefile 的功能的话,这样很糟糕。
4.3 并行执行
GNU make 可以一次执行几条命令。通常make一次执行一条命令,等待其返回,再执行下一条。使用"-j"或"-jobs"可以同时执行多条命令。如果"-j"后跟一个正数,表示一次可以执行的命令条数;如果"-j"之后没有参数,则不限制可执行的命令数。缺省的数量是一。
一个讨厌的问题是如果同时执行多条命令,它们的输出会混在一起;另一个问题是两个进程不能从同一个设备获得输入。
4.4 错误
每条shell 命令返回时,make 会检查其返回状态。如果命令执行成功,则下一条命令被执行,最后一条命令执行完后,规则执行结束。
如果有错误(返回非0 状态),make 放弃当前规则,也可能是所有规则。
有时候命令执行错误并不是问题,比如使用"mkdir"命令确保目录存在:如果目录一存在,则"mkdir"会报告错误,但仍希望make 继续。要忽略命令的错误,在命令之前使用"-"字符,"-"字符在传递给shell 之前被丢弃:
clean:
-rm -f *.o
如果使用"-i"或"-ignore-errors"选项,make 会忽略所有命令产生的错误;一条没有依赖关系的".IGNORE"规则有相同的作用,但"-"更灵活。在忽略错误时,make 将错误也认为是成功,只是通知你命令的退出状态和错误被忽略。如果make 并未告知忽略错误,在错误发生时,表明该目标不能成功。更新,直接或间接依赖于此的目标当然也不能成功;这些目标的命令不会被执行,因为其先决条件不满足。
通常make 会立即以非0 状态退出。然而,如果给定"-k"或"-keep-going"选项,make 在退出前会处理其它的依赖关系,进行必要的更新。例如,在编译一个目标文件遇到错误,"make -k"会继续编译其它的目标文件。通常认为你的目的是更新指定的目标,当make 知道这是不可能时,会立即报告失败;"-k"选项指示真正目的是测试更新程序的更多可能性:在编译之前找出更多不相关的问题。
如果命令失败了,假设它更新的目标文件,这个文件是不完整的不能使用-至少不是完全更新的。但文件的最后修改时间表明已经是最新的,下一次make 运行时,不会再更新这个文件。这种情况和命令被kill 相同;则通常情况下在命令失败时将目标删除是正确的;当".DELETE_ON_ERROR"是目标时make 帮你做这件事。虽然你总是希望make 这么做,但这不是过去的习惯;所以必须显式要求make 这样做(其它的make 自动这样做)。
4.5 中断make
如果make 执行命令时遇到错误,可能会删除命令更新的目标文件: make 检查文件的修改时间是否变化。删除目标的目的是确保make 下次执行时重新生成它。
为什么这样做?假设在编译器运行时按了"Ctrl-c",此时编译器写生成目标文件"foo.o"。"Ctrl-c" kill 了编译器,留下一个不完整的文件,但它的修改时间比源文件"foo.c"新;此时make 也受到"Ctrl-c"信号删除这个不完整的文件,
如果make 不这样做,下次make 运行时认为"foo.o"不需要更新,会在链接时出现奇怪的错误。
可以使用".PRECIOUS"规则来防止目标文件被删除。在make 更新目标时,会检测其是否为".PRECIOUS"的依赖,决定在命令出错或中断时是否删除该目标。如果你希望目标的更新是原子操作,或是用来记录修改时间,或必须一直存在防止其它类型的错误,这些理由使得你必须这样做。
4.6 递归使用
递归使用make 就是在makefile 中使用make 命令。这种技术在你将一个大系统分解为几个子系统,为每个自系统提供一个makefile 时有用处。比如有一个子目录"subdir"中有自己的makefile,希望make 在自己目录中运行,可以这样做:
subsystem:
cd subdir; $(MAKE)
或者
subsystem:
$(MAKE) -C subdir
可以照抄这个例子来递归使用make
4.6.1‘MAKE"变量
递归的make 必须使用"MAKE"变量,不是显式的make 命令:
subsystem:
cd subdir; $(MAKE)
该变量的值是被调用的make的名字。在命令中使用"MAKE"有特殊的功能:它改变了"-t" ("--touch"), "-n" ("--just-print")和"-q" ("--question")选项的含义。使用上例来考虑"make -t"命令("-t"选项将目标标记为最新但不运行命令),"-t"选项的功能,该命令将创建一个"subsystem"文件,实际希望的操作是运行"cd subdir; make -t";但这回执行命令,与"-t"的原意不符。
这个特殊功能做了期望的工作。当命令行包含变量"MAKE"时,选项"-t","-n"和"-q"并不适用。不管这些导致不会执行命令的标志,包含"MAKE"变量的命令始终会执行。正常的"MAKEFLAGS"机制将这些标志传递到子make,
这样打印命令的请求被传播到子系统中。
4.6.2 传递变量到子make
上级(top-level)make 中的变量可以显式通过环境传递到子make中。在子make中,这些变量被缺省定义,但不会重载子makefile 中的定义除非使用"-e"选项向下传递,或输出变量,make 在运行命令时将其加入到环境变量中;子make 可以使用环境变量来初始化变量表。 除非显式要求,make 只输出初始环境中或命令行设置的变量而且变量名只由字母,数字和下划线组成。一些shell 不能处理有其它字符的环境变量。特殊变量"SHELL","MAKEFLAGS"总是输出,如果"MAKEFILE"变量有值,也会输出。Make 自动通过"MAKEFLAGS"来输出命令行定义的变量。
如果想要输出特定变量,使用"export"指令:
export VARIABLE ...
如果要阻止输出一个变量,使用"unexport"指令:
unexport VARIABLE ...
为方便起见,可以在定义变量时输出它:
export VARIABLE = value
和
VARIABLE = value
export VARIABLE
作用相同。
如果要输出所有的变量,使用"export"指令本身就可以了。
变量"MAKELEVEL"在一级一级传递时会改变,这个变量的值是表示嵌套层数的字符串,顶级"make"是,变量的值为"0";子make 的值为"1";子子make 的值为"2",依此类推。
"MAKELEVEL"的用途是在条件指令中测试它,这样写出在递归运行时和直接运行时表现不同的makefile。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -