LV02-05-Makefile-04-隐含规则和模式规则

本文主要是makefile——隐含规则和模式规则相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
Makefile 跟我一起写Makefile
点击查看相关文件下载
--- ---
点击查看所需测试文件

各文件所在目录的结构如下:

1
2
3
4
5
6
.
├── main.c
├── Makefile
├── test1.c
├── test2.c
└── test.h
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include "test.h"

int main(int argc, const char *argv[])
{
printf("This is main file!\n");
test1Fun();
test2Fun();
return 0;
}
1
2
3
4
5
6
#include <stdio.h>

void test1Fun()
{
printf("This is test1.c file\n");
}
1
2
3
4
5
6
#include <stdio.h>

void test2Fun()
{
printf("This is test2.c file\n");
}
1
2
void test1Fun();
void test2Fun();

一、隐含规则

1.什么是隐含规则

在我们使用 Makefile 时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译 C/C++ 的源程序为中间目标文件( Unix 下是 [.o] 文件, Windows 下是 [.obj] 文件)。

隐含规则”就是一种惯例,make 会按照这种“惯例”心照不喧地来运行,即便我们的 Makefile 中没有书写这样的规则。例如,把 [.c] 文件编译成 [.o] 文件这一规则,我们根本就不用写出来, make 会自动推导出这种规则,并生成我们需要的 [.o] 文件。

“隐含规则”会使用一些系统预定义的变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量“ CFLAGS ”可以控制编译时的编译器参数。我们还可以通过“模式规则”(后边会说明)的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们 Makefile 的兼容性。

2.使用隐含规则

我们在测试目录中穿件如下内容的 Makefile 文件:

1
2
3
4
5
6
7
8
OBJ = main.o test1.o test2.o

main: ${OBJ}
gcc $(OBJ) -o main

.PHONY: clean
clean:
rm -rf *.o main

然后在终端执行 make 命令,会看到有如下输出:

1
2
3
4
cc    -c -o main.o main.c
cc -c -o test1.o test1.c
cc -c -o test2.o test2.c
gcc main.o test1.o test2.o -o main

由此可以看出,即便我们没有写依赖目标的规则, Makefile 依然帮我们推倒出了所需要的 main.o 、 test1.o 和 test2.o 。前边学习变量的时候笔记中有说明,系统预定义变量中有一个 CC ,它的值就是 cc ,而这里隐含规则的自动推导使用的就是系统的预定义变量了。这已经是“约定”好了的事了, make 和我们约定好了用 C 编译器 cc 生成 [.o] 文件的规则,这就是隐含规则。

如果我们为 [.o] 文件书写了自己的规则,那么 make 就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行 ,如下边的 Makefile :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
OBJ = main.o test1.o test2.o

main: ${OBJ}
gcc $(OBJ) -o main
main.o: main.c test.h
gcc -c main.c -o main.o
test1.o: test1.c test.h
gcc -c test1.c -o test1.o
test2.o: test2.c test.h
gcc -c test2.c -o test2.o

.PHONY: clean
clean:
rm -rf *.o main

我们执行 make 命令的话,就会看到如下提示信息:

1
2
3
4
gcc -c main.c -o main.o
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc main.o test1.o test2.o -o main

3.取消隐含规则

我们有时候不想使用隐含规则的话,我们就可以在执行 make 命令的时候加上 -r 参数来取消隐含规则:

1
2
3
make -r
# 或者
make --no-builtin-rules

当然,即使是我们指定了 -r 参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了后缀规则来定义的,所以,只要隐含规则中有“后缀列表”(也就一系统定义在目 标 .SUFFIXES 的依赖目标 ),那么隐含规则就会生效 。 默认的后缀列表是:

1
2
.out、 .a、 .ln、 .o、 .c、 .cc、 .C、 .p、 .f、 .F、 .r、 .y、 .l、 .s、 .S、 .mod、 .sym、
.def、 .h、 .info、 .dvi、 .tex、 .texinfo、 .texi、 .txinfo、 .w、 .ch .web、 .sh、 .elc、 .el

4.常用隐含规则

4.1编译 C 程序

[n].o 的目标的依赖目标会自动推导为 [n].c ,其生成命令是:

1
$(CC) –c $(CPPFLAGS) $(CFLAGS)

4.2编译 C++ 程序

[n].o 的目标的依赖目标会自动推导为 [n].cc 或是 [n].C (建议使用 .cc 作为 C++ 源文件的后缀,而不是 .C ),其生成命令是:

1
$(CXX) –c $(CPPFLAGS) $(CFLAGS)

4.3汇编和汇编预处理

[n].o 的目标的依赖目标会自动推导为 [n].s ,默认使用编译器 as ,其生成命令是:

1
$(AS) $(ASFLAGS)

[n].s” 的目标的依赖目标会自动推导为 [n].S ,默认使用 C 预编译器 cpp ,其生成命令是:

1
$(AS) $(ASFLAGS)

4.4链接 Object 文件

[n] 目标依赖于 .o ,通过运行 C 的编译器来运行链接程序生成(一般是 ld ), 其生成命令是:

1
$(CC) $(LDFLAGS) [n].o $(LOADLIBES) $(LDLIBS)

这个规则对于只有一个源文件的工程有效,对多个 Object 文件(由不同的源文件生成)的也有效。例如,如下规则:

1
main : test1.o test2.o

并且 main.c 、 test1.c 和 test2.c 都存在时,隐含规则将执行如下命令:

1
2
3
4
cc -c main.c -o main.o
cc -c test1.c -o test1.o
cc -c test2.c -o test2.o
cc main.o test1.o test2.o -o main # 这一句应该是自己写的,上边三条输出是隐含规则完成的

如果没有哪个源文件(如上例中的 main.c )和我们的目标名字(如上例中的 main )相关联,那么,最好还是写出自己的生成规则,不然,隐含规则会报错的。

5.预设变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量,之前的时候其实我们已经做过笔记了,这里再写一下吧,加深印象。我们可以在 Makefile 中改变这些变量的值,或是在 make 的命令行中传入这些值,或是在环境变量中设置这些值。只要设置了这些特定的变量,那么其就会对隐含规则起作用。

点击查看示例

编译 C 程序的隐含规则的命令是

1
$(CC) –c $(CFLAGS)  $(CPPFLAGS)

make 默认的编译命令是 cc ,如果把变量 $(CC) 重定义成 gcc ,把 变量 $(CFLAGS) 重定义成 -g ,那么,隐含规则中的命令全部会以下边的形式执行:

1
gcc –c -g $(CPPFLAGS)

5.1关于命令的变量

点击查看关于命令的变量
AR函数库打包程序。默认命令是“ar”
AS汇编语言编译程序。默认命令是“as”
CCC 语言编译程序。默认命令是“cc”
CXXC++语言编译程序。默认命令是“g++”
CO从 RCS 文件中扩展文件程序。默认命令是“co”
CPPC 程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”
FCFortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”
GET从 SCCS 文件中扩展文件的程序。默认命令是“get”
LEXLex 方法分析器程序(针对于 C 或 Ratfor)。默认命令是“lex”
PCPascal 语言编译程序。默认命令是“pc”
YACCYacc 文法分析器(针对于 C 程序)。默认命令是“yacc”
YACCRYacc 文法分析器(针对于 Ratfor 程序)。默认命令是“yacc –r”
MAKEINFO转换 Texinfo 源文件(.texi)到 Info 文件程序。默认命令是“makeinfo”
TEX从 TeX 源文件创建 TeX DVI 文件的程序。默认命令是“tex”
TEXI2DVI从 Texinfo 源文件创建军 TeX DVI 文件的程序。默认命令是“texi2dvi”
WEAVE转换 Web 到 TeX 的程序。默认命令是“weave”
CWEAVE转换 C Web 到 TeX 的程序。默认命令是“cweave”
TANGLE转换 Web 到 Pascal 语言的程序。默认命令是“tangle”
CTANGLE转换 C Web 到 C。默认命令是“ctangle”
RM删除文件命令。默认命令是“rm –f”

5.2关于参数的变量

点击查看关于参数的变量
ARFLAGS函数库打包程序 AR 命令的参数。默认值是“rv”
ASFLAGS汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)
CFLAGSC 语言编译器参数
CXXFLAGSC++语言编译器参数
COFLAGSRCS 命令参数
CPPFLAGSC 预处理器参数。( C 和 Fortran 编译器也会用到)
FFLAGSFortran 语言编译器参数
GFLAGSSCCS “get”程序参数
LDFLAGS链接器参数。(如:“ld”)
LFLAGSLex 文法分析器参数
PFLAGSPascal 语言编译器参数
RFLAGSRatfor 程序的 Fortran 编译器参数
YFLAGSYacc 文法分析器参数

二、静态模式

1.静态模式作用

在 Makefile 中,一个规则中可以有多个目标,规则所定义的命令对所有的目标有效,不同的目标可以根据目标文件的名字来自动构造出依赖文件。一个具有多目标的规则相当于多个规则,这样使用多目标可以使 Makefile 文件变得简洁。

2.静态模式语法

1
2
<targets ...>: <target-pattern>: <prereq-patterns ...>
<Tab>[option]<commands>

【参数说明】

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
targets-pattern是指明了 targets 的模式,也就是的目标集模式。
prereq-patterns是目标的依赖模式,它对 targets-pattern 形成的模式再进行一次依赖目标的定义。
<Tab>表示命令的开始(命令前边一定要有Tab)
option@输出的信息中,不要显示此行命令(make执行过程中,默认会显示所执行的命令)。
-忽略当前此行命令执行时候所遇到的错误。如果不忽略,make在执行命令的时候,如果遇到error,会退出执行的,加上减号后,即便此行命令执行中出错,比如删除一个不存在的文件等,那么也会继续执行make。
commandmake 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行

<target-parrtern> 定义成 %.o ,意思是我们的 <target> 集合中都是以 .o 结尾的,而如果我们的 <prereq-parrterns> 定义成 %.c , 意思是对 <target-parrtern> 所形成的目标集进行二次定义,其计算方法是,取 <target-parrtern> 模式中的 % (也就是去掉了 [.o] 这个结尾),并为其加上 [.c] 这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有 % 这个字符,如果文件名中有 % ,那么可以使用反斜杠 \ 进行转义,来标明真实的 % 字符。

3.实例说明

说真的,看完《跟我一起写Makefile》的上关于静态模式的语法后,属实是一脸懵,还是写一个实例,免得下次看到又不理解了。还是使用笔记开头的几个测试文件,我们修改 makefile 内容如下:

1
2
3
4
5
6
7
8
9
10
11
OBJ = main.o test1.o test2.o

main: ${OBJ}
gcc $(OBJ) -o main

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

.PHONY: clean
clean:
rm -rf *.o main

然后我们在终端执行 make 命令,会看到如下信息输出:

1
2
3
4
cc -c  main.c -o main.o
cc -c test1.c -o test1.o
cc -c test2.c -o test2.o
gcc main.o test1.o test2.o -o main

指明了我们的目标从 $(OBJ) 中获取, %.o 表明要所有以 .o 结尾的目标,也就是 main.o 、 test1.o 和 test2.o ,也就是变量 $(OBJ) 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 main 、 test1 和 test2 ,并为其加上 .c 的后缀,于是,我们的依赖目标就是 main.c 、 test1.c 和 test2.c 。而命令中的 $< 和 $@ 则是自动化变量, $< 表示所有的依赖目标集(也就是“ main.c 、 test1.c 和 test2.c ), $@ 表示目标集(也就是 main.o 、 test1.o 和 test2.o ),总结一下就是:

OBJ 目标的值列表: main.o test1.o test2.o
main: $(OBJ) 最终目标,这个最终目标有依赖文件,${OBJ}表示的是就是所依赖的文件集合,下边的是最终目标的生成命令
$(OBJ): %.o: %.c $(OBJ) 指明了目标为 OBJ,即 main.o test1.o test2.o
%o 表明是所有以 .o 结尾的目标文件,也就是 main.o 、 test1.o 、和 test2.o ,也就是变量 $OBJ 集合的模式,
%c 依赖模式 %.c 则取模式 %.o 的 % ,也就是 main 、 test1 、和 test2 ,并为其加上 .c 的后缀,于是,我们的依赖就是 main.c 、 test1.c 、和 test2.c
$< 表示所有的依赖目标集(也就是main.c、test1.c、和test2.c)
$@ 表示目标集(也就是main.o、test1.o、和test2.o)

于是,上面的规则展开后等价于下面的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
OBJ = main.o test1.o test2.o

main: ${OBJ}
gcc $(OBJ) -o main
main.o: main.c test.h
$(CC) -c $(CFLAGS) main.c -o main.o
test1.o: test1.c test.h
$(CC) -c $(CFLAGS) test1.c -o test1.o
test2.o: test2.c test.h
$(CC) -c $(CFLAGS) test2.c -o test2.o

.PHONY: clean
clean:
rm -rf *.o main

由此可以看出,使用了静态模式的 Makefile 文件已经简单了很多了,可以想象,如果有上百个 .o 和 .c 文件,这样做,只需要 4 行就可以完成所有 .o 文件的生成了,无疑给我们带来了巨大便利。

三、模式规则

前边学习隐含规则的时候有提到,一行规则可以使用模式规则来定义,什么是模式规则呢?又该如何使用呢?

1.匹配符 %

在 Makefile 中 % 被称为匹配符,也可以称之为模式字符

make 命令允许对文件名进行类似正则运算的匹配,主要用到的匹配符是 % ,它可以匹配任何非空字符串,主要应用在模式规则中。在依赖目标中同样可以使用 % ,只是依赖目标中的 % 的取值,取决于其目标。

2.模式规则

2.1模式规则格式

首先,还是要来了解以下什么叫模式规则。模式规则的一般格式如下:

1
2
%.o : %.c
<Tab>[option]command

【参数说明】

%.o表示匹配所有的 .o 文件
%.c表示匹配所有的 .c 文件
<Tab>表示命令的开始(命令前边一定要有Tab)
option@输出的信息中,不要显示此行命令(make执行过程中,默认会显示所执行的命令)。
-忽略当前此行命令执行时候所遇到的错误。如果不忽略,make在执行命令的时候,如果遇到error,会退出执行的,加上减号后,即便此行命令执行中出错,比如删除一个不存在的文件等,那么也会继续执行make。
commandmake 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行

【注意】

(1)模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。

(2)模式字符 % 的匹配和替换发生在规则中所有变量和函数引用展开之后,变量和函数的展开一般发生在 make 读取 Makefile 时,而模式规则中的 % 的匹配和替换则发生在 make 执行时。

(3)目标中的 % 定义表示对文件名的匹配, % 表示长度任意的非空字符串。例如: %.c 表示以 .c 结尾的文件名(文件名的长度至少为 3 ), 而 s.%.c 则表示以 s. 开头, .c 结尾的文件名(文件名的长度至少为 5 )。

(4)如果 % 定义在目标中,那么,目标中的 % 的值决定了依赖目标中的 % 的值,也就是说,目标中的模式的 % 决定了依赖目标中 % 的样子。例如, %.o 匹配到了文件 main.o ,那么, %.c 就代表着 main.c 。

  • 一个小实例
1
2
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

上边的规则将把所有的 .c 文件都编译成 .o 文件.

2.2模式规则要求

模式规则与普通规则类似,但是模式规则有以下要求:

  • (1)目标名

对于目标名来说,目标名中需要包含一个模式字符 % ,包含有模式字符 % 的目标被用来匹配一个文件名。在目标文件名中 % 匹配的部分称为,比如,目标名为 %.o ,匹配到了一个 main.o 文件,那么 % 就代表 main ,也就是茎。

若目标模式中包含斜杠(也就是目录部分),在进行目标文件匹配时,文件名中包含的目录字符串在匹配之前被移除,只进行基本文件名的匹配;匹配成功后,再将目录加入到匹配之后的字符串之前形成茎。

点击查看实例说明

目标模式为 m%n ,文件 src/main 和这个目标模式相匹配,那么茎就是 src/ai 。

模式规则中依赖文件的产生是:首先使用茎的非目录部分( ai )替代依赖文件中的模式字符 % ,之后再将目录部分( src/ )加入到形成的依赖文件名之前构成依赖文件的全路径名。这里如果模式规则的依赖模式为 t%t ,则那么目标 src/main 对应的依赖文件就为 src/tait 。

  • (2)依赖文件

依赖文件中同样可以使用 % ,依赖文件中模式字符 % 的取值情况由目标中的 % 来决定。例如,对于模式规则 %.o : %.c ,它表示的含义是:所有的 .o 文件依赖于对应的 .c 文件。例如,如果要生成的目标 %.o 是 a.o b.o 那么 %.c 就是 a.c b.c 。

模式规则中的依赖文件也可以不包含模式字符 % 。当依赖文件名中不包含模式字符 % 时,其含义是所有符合目标模式的目标文件都依赖于一个指定的文件(例如: %.o : test.h ,表示所有的 .o 文件都依赖于头文件 test.h )。

  • (3)多目标

一个模式规则可以存在多个目标。多目标的模式规则和普通多目标规则有些不同:

普通多目标规则的处理是将每一个目标作为一个独立的规则来处理,所以多个目 标就就对应多个独立的规则(这些规则各自有自己的命令行,各个规则的命令行可能相同)。

对于多目标模式规则来说,所有规则的目标共同拥有依赖文件和规则的命令行,当文件符合多个目标模式中的任何一个时,规则定义的命令就有可能将会执行;因为多个目标共同拥有规则的命令行,因此一次命令执行之后,规则不会再去检查是否需要重建符合其它模式的目标。

点击查看多目标的相关实例
点击查看相关文件内容
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(int argc, const char *argv[])
{
printf("This is main file!\n");

return 0;

}
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(int argc, const char *argv[])
{
printf("This is test file!\n");

return 0;

}

Makefile 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
Objects = main.o test.o
CFLAGS := -Wall
%x : CFLAGS += -g
%.o : CFLAGS += -O2
%.o %.x : %.c
$(CC) $(CFLAGS) $< -o $@


.PHONY: clean
clean:
rm -rf *.o main

然后那我们执行 make main.o main.x 时,会看到只有一个文件 main.o 被创建了,同时 make 会有以下提示,

1
2
cc -Wall -O2 main.c -o main.o
make: 对“main.x”无需做任何事。

但是实际上, main.x 文件并未被创建。这个例子表明了多目标的模式规则在 make 处理时是作为一个整体来处理的,这是多目标模式规则和多目标的普通规则的不同之处。

【注意】

(1)模式规则在 Makefile 中的顺序需要注意,当一个目标文件符合多个模式规则的目标时, make 将会按照第一个找到的作为重建它的规则。
(2)在 Makefile 中指定的模式规则会覆盖隐含的模式规则。就是说在 Makefile 中明确指定的会替代隐含的模式规则。
(3)依赖文件存在或者被提及的规则,优先于那些需要使用隐含规则来创建其依赖文件的规则。

3.实例说明

其实这个实例与静态模式的实例是一样的,静态模式的语法是建立在模式规则的基础上的,依靠模式规则完成静态模式语法。还是使用笔记开头的几个测试文件,我们修改 makefile 内容如下:

1
2
3
4
5
6
7
8
9
10
11
OBJ = main.o test1.o test2.o

main: ${OBJ}
gcc $(OBJ) -o main

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

.PHONY: clean
clean:
rm -rf *.o main

然后我们在终端执行 make 命令,会看到如下信息输出:

1
2
3
4
cc -c  main.c -o main.o
cc -c test1.c -o test1.o
cc -c test2.c -o test2.o
gcc main.o test1.o test2.o -o main

指明了我们的目标从 $(OBJ) 中获取, %.o 表明要所有以 .o 结尾的目标,也就是 main.o 、 test1.o 和 test2.o ,也就是变量 $(OBJ) 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 main 、 test1 和 test2 ,并为其加上 .c 的后缀,于是,我们的依赖目标就是 main.c 、 test1.c 和 test2.c 。而命令中的 $< 和 $@ 则是自动化变量, $< 表示所有的依赖目标集(也就是“ main.c 、 test1.c 和 test2.c ), $@ 表示目标集(也就是 main.o 、 test1.o 和 test2.o ),总结一下就是:

OBJ 目标的值列表: main.o test1.o test2.o
main: $(OBJ) 最终目标,这个最终目标有依赖文件,${OBJ}表示的是就是所依赖的文件集合,下边的是最终目标的生成命令
$(OBJ): %.o: %.c $(OBJ) 指明了目标为 OBJ,即 main.o test1.o test2.o
%o 表明是所有以 .o 结尾的目标文件,也就是 main.o 、 test1.o 、和 test2.o ,也就是变量 $OBJ 集合的模式,
%c 依赖模式 %.c 则取模式 %.o 的 % ,也就是 main 、 test1 、和 test2 ,并为其加上 .c 的后缀,于是,我们的依赖就是 main.c 、 test1.c 、和 test2.c
$< 表示所有的依赖目标集(也就是main.c、test1.c、和test2.c)
$@ 表示目标集(也就是main.o、test1.o、和test2.o)

于是,上面的规则展开后等价于下面的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
OBJ = main.o test1.o test2.o

main: ${OBJ}
gcc $(OBJ) -o main
main.o: main.c test.h
$(CC) -c $(CFLAGS) main.c -o main.o
test1.o: test1.c test.h
$(CC) -c $(CFLAGS) test1.c -o test1.o
test2.o: test2.c test.h
$(CC) -c $(CFLAGS) test2.c -o test2.o

.PHONY: clean
clean:
rm -rf *.o main

四、 Makefile 通配符

Makefile 可以使用 Shell 命令,所以 Shell 中的通配符在 Makefile 中也同样适用。

1.常用通配符

通配符使用说明
*匹配0个或者是任意个字符
?匹配任意一个字符
[]我们可以指定匹配的字符放在 "[]" 中

【注意】

(1)通配符可以用在命令中,例如,

1
2
3
.PHONY:clean
clean:
rm -rf *.o main

(2)通配符可以用在规则中,例如,

1
2
main:*.c
gcc -o $@ $^

但是,不可以通过引用变量的方式来使用。例如,

1
2
3
4
# 以下是不被允许的
OBJ=*.c
main:${OBJ}
gcc -o $@ $^

如果我们就是相要通过引用变量的话,我们要使用一个函数 wildcard ,这个后边学习到函数的时候会详细说明。

2. % 与 *

上边学习模式规则的时候,我们接触到了 % ,它可以匹配任意非空字符串, * 也可以用于规则中来匹配字符串,那么它们是完全一样的嘛?当然不是一样的啦。其实我们可以尝试一下以下两种规则有什么不同,我们还是使用笔记开头的测试文件:

Makefile 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
OBJ = main.o test1.o test2.o
# 可执行程序 main
main: ${OBJ}
gcc $(OBJ) -o main

%.o: %.c
gcc -c $< -o $@

.PHONY: clean clean-o
clean: clean-o
rm -rf main main-copy
clean-o:
rm -rf *.o

运行 make 命令后,会正常生成所有文件。终端输出的信息如下:

1
2
3
4
gcc -c main.c -o main.o
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc main.o test1.o test2.o -o main

接下来我们分析一下这个文件中的 %.o: %.c , %.o 匹配所有的 .o 目标文件 main.o 、 test1.o 和 test2.o ,然后得到相应的茎,也就是 % 所匹配的内容 main 、 test1 和 test2 ,然后后边的 %.c 就分别代表 main.c 、 test1.c 和 test2.c 。

Makefile 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
OBJ = main.o test1.o test2.o
# 可执行程序 main
main: ${OBJ}
gcc $(OBJ) -o main

*.o: *.c
gcc -c $< -o $@

.PHONY: clean clean-o
clean: clean-o
rm -rf main main-copy
clean-o:
rm -rf *.o

运行 make 命令后,会正常生成所有文件。终端输出的信息如下:

1
2
3
4
cc    -c -o main.o main.c
cc -c -o test1.o test1.c
cc -c -o test2.o test2.c
gcc main.o test1.o test2.o -o main

会发现,这个输出信息好奇怪啊,为什么是 cc 开头,我的命令不是 gcc 嘛,据推测,它默认还是使用了隐含规则来生成文件。

不过我们依然可以分析一下 * 的作用, *.c 就表示只要是 .c 文件,全都是我要找的,找到后就进行编译。

总之, % 与 * 都是匹配字符串,但是,他们的工作方式却不同,要注意一下。

五、隐含规则搜索算法

这段说真的,我没看懂,但是可能对之后理解或者编写 Makefile 有一定的帮助,笔记记在这里慢慢理解把,哈哈。

比如我们有一个目标叫 T 。下面是搜索目标 T 的规则的算法。请注意,在下面,我们没有提到后缀规则,原因是,所有的后缀规则在 Makefile 被载入内存时,会被转换成模式规则。如果目标是 archive(member) 的函数库文件模式,那么这个算法会被运行两次,第一次是找目标 T ,如果没有找到的话,那么进入第二次,第二次会把 member 当作 T (也就是目标) 来搜索。

  • 1、把 T 的目录部分分离出来。叫 D ,而剩余部分叫 N 。

例如:若 T 是 src/foo.o ,那么, D 就是 src/ , N 就是 foo.o 。

  • 2、创建所有匹配于 T 或是 N 的模式规则列表。

  • 3、如果在模式规则列表中有匹配所有文件的模式,如 % ,那么从列表中移除其它的模式。

  • 4、移除列表中没有命令的规则。

  • 5、对于第一个在列表中的模式规则:

(1)推导其”茎” S , S 应该是 T 或是 N 匹配于模式中 % 非空的部分。

(2)计算依赖文件。把依赖文件中的 % 都替换成”茎” S 。如果目标模式中没有包含斜框字符,而把 D 加在第一个依赖文件的开头。

(3)测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件, 那么这个文件就叫”理当存在”)。

(4)如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。

  • 6、如果经过第 5 步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则

(1)如果规则是终止规则,那就忽略它,继续下一条模式规则。

(2)计算依赖文件。 (同第 5 步)

(3)测试所有的依赖文件是否存在或是理当存在。

(4)对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到 。

(5)如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。

  • 7、如果没有隐含规则可以使用,查看 .DEFAULT 规则,如果有,采用,把 .DEFAULT 的命令给 T 使用。一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。