makefile 文件基本规则

    makefile 文件中包含了一组用来编译应用程序的规则。make 命令执行时所看到的第一项规则,会被作为默认规则使用。一项规则可分成三个部分:工作目标(target)、它的必要条件(prerequisite)以及所要执行的命令(commend)。
# 注释:没指定必要条件时,只有在工作目标代表的文件不存在时才会进行更新。
target1 [target2...]: [prereq1 prereq2...]
    command1
    command2
    ...

    其中,工作目标(target)是一个必须建造的的文件或进行的事情;必要条件或依存对象是工作目录得以创建之前,必须已存在的那些文件;而所要执行的命令则是必要条件成立时将会创建工作目标的那些 shell 命令。这三部分均支持 shell 通配符模式,不过当模式出现在工作目标或必要条件中时,是由 make 进行通配符的扩展,而当模式出现在命令中时,是由 subshell 来进行扩展的。
    当 make 处理某项规则时,它首先会找出必要条件和工作目标中所指定的文件。如果必要条件中存在关联到其他规则的文件,则 make 会先完成相应规则的更新动作。如果必要条件中存在时间戳在工作目标的之后的文件,则 make 会执行命令以便重新建立工作目标。脚本会被传递给 shell 并在其 subshell 中运行。
    示例:
count_words: count_words.o lexer.o -lfl
        gcc count_words.o lexer.o -lfl -o count_words
count_words.o: count_words.c
        gcc -c count_words.c
lexer.c: lexer.l
        flex -t lexer.l > lexer.c

    但执行 make 命令时,将会看到这样的输出:
$ make
gcc -c count_words.c
flex -t lexer.l > lexer.c
gcc -c lexer.c
gcc count_words.o lexer.o -lfl -o count_words

    可看出 make 是先把必要条件完成后,才能实现最终的工作目标。注意这里的 lexer.c 文件是由 flex 程序产生的。此处指定了“-lfl”这个参数,“-l”参数要求 gcc 必须将其所指定的系统程序库链接进应用程序,这里代表实际的程序库名称为 libfl.a。根据 GNU make 规定:当 -l<NAME> 形式的必要条件被发现时,make 会搜索 libNAME.so 形式的文件;如果找不到,make 接着会搜索 libNAME.a 形式的文件。
    如果要更新另一个(或多个)不同的工作目标,应在命令行上明确指定,如:
        $ make lexer.c
    make 提供了许多命令行选项,其中较为有用的选项之一是“--just-print/-n”,用来要求 make 显示它将为特定工作目标执行的命令,但不要实际执行它们。
    另外,有时我们可能希望提供类似如下的假想工作目标:
...
clean:
        rm -f *.o lexer.c

    因为大多数假想工作目标并未指定必要条件,而 make 又无法区分文件形式的工作目标与假想工作目标,所以如果当前目录中刚好出现与假想工作目标同名的文件,make 将会在它的依附图中建立该文件与假想工作目标的关系,从而可能导致对应的工作目标总是会被视为已经更新,也就永远不会再执行相应的命令。
    为避免这个问题,GNU make 提供了一个特殊的工作目标“.PHONY”,用来表面该工作目标不是一个真正的文件,总是把工作目标标记为尚未更新。当想声明假想工作目标时,只要将该工作目标指定成“.PHONY”的一个必要条件即可:
...
.PHONY: clean
clean:
        rm -f *.o lexer.c

    以假想工作目标作为实际文件的一个必要条件似乎不太有意义,因为假想工作目标总是尚未更新,这总会使得该实际文件(工作目标)被重新建立。但将其作为假想工作目标的必要条件却有些用处。比如,all 工作目标常会用来指定所要编译的一串程序:
...
.PHONY: all
all: bash bashbug

    这样,all 工作目标将会创建 bash 和 bashbug(一个错误报告工具)。
    此外,还可将假想工作目标作为内置在 makefile 里的 shell 脚本来用,让 make 在进行实际工作目标之前调用假想工作目标所代表的脚本。比如,假如我们很在意磁盘空间的使用情况,因而在进行磁盘密集的工作之前,想要显示磁盘尚有多少空间可用,则可以这样写:
...
.PHONY: make-documentation
make-documentation: df
        javadoc ...
.PHONY: df
df:
        df -k . | awk 'NR == 2 {printf("%d available\n", $$4)}'

    makefile 中支持使用变量,引用形式是 $(varaible_name) 或 ${varaible-name},当变量名只有一个字符时可以不用括号,而且变量名可以包含大多数字符(包括标点符号)。为了方便取用工作目标以及必要条件中的元素,当规则相符时,make 会设定自动变量(因而也只能应用在规则中的命令脚本部分)。以下是几个常用的自动变量:
自动变量 描述
$@ 工作目标的文件名
$% 档案文件成员结构中的文件名元素,可通过 archive(member) 这样的语法在档案文件 archive 中指定名为 member 的成员
$< 第一个必要条件的文件名
$? 时间戳在工作目标之后的所有必要条件,并以空格隔开这些必要条件
$^ 所有必要条件的文件名,并以空格隔开,而且会删除重复的文件
$+ 同 $^,但会包含重复的文件名。
$* 工作目标的主文件名(即无扩展名部分)。建议不要在模式规则以外使用

    此外,为了兼容其它 make 版本,这些变量都具有两个变体。其中一个只会返回值的目录部分,是通过在原有的符号之后附加 D 这个字母实现的,如 $(@D);另一个只返回值的文件部分,是通过附加 F 字母实现的,如 $(@F)。
    现在先前的那个 makefile 文件可写出如下形式:
count_words: count_words.o lexer.o -lfl
        gcc $^ -o $@
count_words.o: count_words.c
        gcc -c $<
lexer.c: lexer.l
        flex -t $< > $@

    默认情况下,make 只会在当前目录下寻找工作目标和必要条件。所以当需要的文件出现在多个目录时,就需要借助其他手段。此时就可考虑使用 VPATH 变量和 vpath 指令了。
    VPATH 变量的内容是一份目录列表。make 在当前目录下找不到的文件就会去搜索该目录列表。其定义格式为:
        VPATH = dir1 [dir2 ...]
    但是 make 只取用第一个找到的文件,因此当多个目录中出现同名文件时可能会出乎我们的预期。此时就可以使用 vpath 指令了,其语法如下:
        vpath pattern directory-list
    举个例子,假设我们所需的头文件在 ./include/ 目录下,而源文件在 ./src/ 目录下,我们就可以类似这样编写 makefile,通过 vpath 告诉 make 在目录 src 中搜索“.c”和“.l”文件,而在 include 目录中搜索“.h”文件:
# VPATH = src include        # 这是使用 VPATH 变量的情况
# CPPFLAGS = -I include      # 指定隐含编译规则
vpath %.l %.c src
vpath %.h include
count_words: count_words.o counter.o lexer.o -lfl
        gcc $^ -o $@
count_words.o: count_words.c counter.h
        gcc -c $<
lexer.c: lexer.l
        flex -t $< > $@

    注意,这里的“%.c”等用到了模式规则,该规则中的“%”大体上等效于 shell 中的“*”,可以代表任意多个字符。“%”可以放在文件名中的任何地方,不过只能出现一次。另外,你还有可能用到只有一个“%”的模式,通常用来表示可执行程序,因为 Unix 上的可执行文件一般不需要扩展名。

猜你喜欢

转载自aisxyz.iteye.com/blog/2383820