Makefile

在Linux系统中,可以通过make指令完成Makefile文件的执行,make指令是一个命令工具,是一个解释Makefile文本的命令工具,也可以说它是一个工程管理器软件工具。
如果打算使用该指令,则需要在Linux系统中安装该指令:sudo apt install make,如下:
image
可以看到,用户在编写makefile文件的时候,该文件的名称应该是makefile或者Makefile,建议大家使用Makefile作为该脚本文件的名称,注意:该文件是没有拓展名的!!!
注意:make是一个shell命令,用于执行makefile文件中的命令(gcc xxx.c -o xxx),前提是执行make指令的时候,当前路径下需要有makefile文件,如果没有makefile文件,则指令make指令时会报错。
通过GNU组织提供的Make手册可以知道,Makefile文件的基本规则是有四部分组成,分别是目标、先决条件、制表符、命令,具体的规则如下所示:
image
image

注意:
(1)<tab符>即是制表符,即Tab键,不能用空格代替;也是因为这个制表符,make才知道后面是一个需要执行的shell命令。
(2)目标(需要有)以及其后的依赖列表(可以没有),以及其下的shell命令(可以没有),统称为一套规则。
(3)对于是编译代码的Makefile配置文件而言,如果依赖列表中有一个及以上的文件的时间戳比目标文件新,则执行编译源代码命令,否则不执行编译源代码命令。

一般编译过程:
image:a.o b.o x.o y.o
gcc a.o b.o x.o y.o -o image
a.o:a.c
gcc a.c -o a.o -c
b.o:b.c
gcc b.c -o b.o -c
x.o:x.c
gcc x.c -o x.o -c
y.o:y.c
gcc y.c -o y.o -c
可以看到不够简便

所以Makefile中有以下几种变量:
自定义变量
系统预定义变量
自动化变量

在Makefile中变量的特征有以下几点:
1)变量和函数的展开(除规则的命令行以外),是在make读取Makefile文件时进行的,这里的变量包括了使用“=”定义和使用指示符“define”定义的变量。

2)变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、搜索源文件的目录列表、编译输出的目录列表和所有我们能够想到的事物。

3)变量名不能包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。需要注意的是,尽管在GNU make中没有对变量的命名有其它的限制,但定义一个包含除字母、数字和下划线以外的变量的做法也是不可取的,因为除字母、数字和下划线以外的其它字符可能会在以后的make版本中被赋予特殊含义,并且这样命名的变量对于一些Shell来说不能作为环境变量使用。

4)变量名是大小写敏感的。变量“foo”、“Foo”和“FOO”指的是三个不同的变量。Makefile传统做法是变量名是全采用大写的方式。推荐的做法是在对于内部定义的一般变量(例如:目标文件列表objects)使用小写方式,而对于一些参数列表(例如:编译选项CFLAGS)采用大写方式,这并不是要求的。但需要强调一点:对于一个工程,所有Makefile中的变量命名应保持一种风格,否则会显得你是一个蹩脚的开发者(就像代码的变量命名风格一样),随时有被鄙视的危险。

5)另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。称它们为自动化变量。像“<”、“@”、“?”、“*”、“@D”、“%F”、“^D”等等,后面会详述之。

6)变量的引用跟Shell脚本类似,使用美元符号和圆括号,比如有个变量叫A,那么对他的引用则是$(A),有个自动化变量叫@,则对他的引用是$(@),有个系统变量是CC则对其引用的格式是$(CC)。对于前面两个变量而言,他们都是单字符变量,因此对他们引用的括号可以省略,写成$A和$@。

(1)自定义变量,例如:
以上三个变量都是自定义变量,其中变量A包含了一个单词,变量B的值包含了三个单词,变量C的值引用了变量A的值,因此他的值是“apple tree”。如果要将这三个变量的值打印出来,可以这么写:既然是类似宏,所以在使用是需要添加特殊符号的: $( )

点击查看代码
gec@ubuntu:~$ cat Makefile -n
     1	A = apple
     2	B = I love China
     3	C = $(A) tree
     4	
     5	all:
     6		@echo $(A)		//echo前面的@代表命令本身不打印出来
     7		@echo $(B)
     8		@echo $(C)
gec@ubuntu:~$ make
apple
I love China
apple tree
使用自定义变量,可以将前面的工程配置文件Makefile中的所有.o文件用一个变量OBJ来代表:
点击查看代码
gec@ubuntu:~$ cat Makefile -n 
     1	OBJ = a.o b.o x.o y.o
     2	
     3	image:$(OBJ)
     4		gcc $(OBJ) -o image
     5	
     6	a.o:a.c
     7		gcc a.c -o a.o -c
     8	b.o:b.c
     9		gcc b.c -o b.o -c
    10	x.o:x.c
    11		gcc x.c -o x.o -c
    12	y.o:y.c
	13		gcc y.c -o y.o -c
	14	clean:
	15  	rm $(OBJ)

(2)系统预定义变量
CFLAGS、CC、MAKE、Shell等等,这些变量已经有了系统预定义好的值,当然我们可以根据需要重新给他们赋值,例如CC的默认值是gcc,当我们需要使用gcc编译器的时候可以直接使用:

点击查看代码
gec@ubuntu:~$ cat Makefile -n 
     1	OBJ = a.o b.o x.o y.o
     2	
     3	image:$(OBJ)
     4		$(CC) $(OBJ) -o image
     5	
     6	a.o:a.c
     7		$(CC) a.c -o a.o -c
     8	b.o:b.c
     9		$(CC) b.c -o b.o -c
    10	x.o:x.c
    11		$(CC) x.c -o x.o -c
    12	y.o:y.c
	13		$(CC) y.c -o y.o -c
	14	clean:
	15  	rm $(OBJ)
这样做的好处是:在不同平台中,c编译器的名称也许会发生变化,如果我们的Makefile使用了100处c编译器的名字,那么换一个平台我们只需要重新给预定义变量CC赋值一次即可,而不需要修改100处不同的地方。比如我们换到ARM开发平台中,只需要重新给CC赋值为arm-linux-gcc即可。(用自定义变量覆盖系统预定义,实质是变量的值改变了,不再是原本的,而是一个新值)
点击查看代码
gec@ubuntu:~$ cat Makefile -n 
     1	OBJ = a.o b.o x.o y.o
     2	CC = arm-Linux-gcc		    #此处为自定义变量,覆盖系统预定义变量
     3							
     4	image:$(OBJ)
     5		$(CC) $(OBJ) -o image
     6	
     7	a.o:a.c
     8		$(CC) a.c -o a.o -c
     9	b.o:b.c
    10		$(CC) b.c -o b.o -c
    11	x.o:x.c
    12		$(CC) x.c -o x.o -c
    13	y.o:y.c
	14		$(CC) y.c -o y.o -c
	15	clean:
	16  	rm $(OBJ)

注意:此时的CC就不再是gcc而是交叉工具链arm-Linux-gcc了。Makefile文件中注释使用#号。

常用的系统预定义变量,请看下表:
变量名 含义 备注
AR 函数库打包程序,可创建静态库.a文档。默认是“ar”。 无
AS 汇编程序。默认是“as”。 无
CC C编译程序。默认是“cc”。 无
CXX C++编译程序。默认是“g++”。 无
CPP C程序的预处理器。默认是“$(CC) –E”。 无
RM 删除命令。默认是“rm –f”。 无
ARFLAGS 执行AR命令的命令行参数。默认值是“rv”。 无
ASFLAGS 汇编器AS的命令行参数(明确指定“.s”或“.S”文件时)。 无
CFLAGS 执行CC编译器的命令行参数(编译.c源文件的选项)。 无
CXXFLAGS 执行g++编译器的命令行参数(编译.cc源文件的选项)。 无

(3)自动化变量
<、@、?、#等等,这些特殊的变量之所以称为自动化变量,是因为它们的值会“自动地”发生变化,可以类比普通变量,只要你不给它重新赋值,那么它的值是永久不变的,比如上面的系统预定义CC变量,只要不对它重新赋值,CC永远都等于gcc。
也就是说自动化变量的值是可以改变的,不是固定的。例如@,不能说 @ 的值等于某个固定值,但是它的含义的固定的:@ 代表了其所在规则的目标的完整名称。

有关自动化变量的详细情况,见下表:
@ 代表其所在规则的目标的完整名称
% 代表其所在规则的静态库文件的一个成员名
< 代表其所在规则的依赖列表的第一个文件的完整名称
? 代表所有时间戳比目标文件新的依赖文件列表,用空格隔开
^ 代表其所在规则的依赖列表 同一文件不可重复

  • 代表其所在规则的依赖列表 同一文件可重复,主要用在程序链接时,库的交叉引用场合。
  • 在模式规则和静态模式规则中,代表茎 茎是目标模式中“%”所代表的部分(当文件名中存在目录时,茎也包含目录(斜杠之前)部分。

上述列出的自动量变量中。其中有四个在规则中代表一个文件名(@、<、%和*)。而其它三个的在规则中代表一个文件名的列表。

使用自动化变量,可把之前的Makefile文件修改如下:

点击查看代码
gec@ubuntu:~$ cat Makefile -n 
     1	OBJ = a.o b.o x.o y.o
     2	
     3	image:$(OBJ)
     4		$(CC) $^ -o $(@)
     5	
     6	a.o:a.c
     7		$(CC) $^ -o $(@)-c
     8	b.o:b.c
     9		$(CC) $^ -o $(@) -c
    10	x.o:x.c
    11		$(CC) $^ -o $(@) -c
    12	y.o:y.c
	13		$(CC) $^ -o $(@) -c
	14	clean:
	15  	rm $(OBJ)

静态规则

所谓的静态规则,其工作原理是:$(OBJ)被称为原始列表,即(a.o b.o x.o y.o),紧跟在其后的%.o被称为匹配模式,含义是在原始列表中按照这种指定的模式挑选出能匹配得上的单词(在本例中要找出原始列表里所有以.o为后缀的文件)作为规则的目标。如下所示:

点击查看代码
gec@ubuntu:~$ cat Makefile -n 
     1	OBJ = a.o b.o c.o
     2	
     3	image:$(OBJ)
     4		$(CC) $(OBJ) -o image
     5
     6	$(OBJ):%.o:%.c
     7		$(CC) $(^) -o $(@) l -c -Wal
     8
     9	clean:
    10		$(RM) $(OBJ) image
    11
    12	.PHONY: clean

静态规则工作原理整个过程用下图演示:
image
简单地讲,就是用一个规则来生成一系列的目标文件。接着,第二个冒号后面的内容就是目标对应的依赖,%可以理解为通配符,因此本例中%.o:%.c的意思就是:每一个匹配出来的目标所对应的依赖文件是同名的.c文件,这个过程也用图演示如下:
image
image
总结一下,静态规则是:当规则存在多个目标时,不同的目标可以根据目标文件的名字来自动构造出依赖文件(即只需写出目标名,即可寻找相应同名字的依赖文件)。

posted @ 2025-08-22 16:45  Lè_Sage  阅读(37)  评论(0)    收藏  举报