这篇文章是阅读廖雪峰老师的文章 后的一点笔记和心得

makefile文件学习

规则格式

规则格式如下

1
2
<target> : <prerequisites> 
[tab] <commands>

格式解释:

  • 目标target : 目标是规则名,常常设置为文件名或者操作目的,同时一条规则可以有多个目标,可以理解为,多个目标有相同的command,然后就可以合并写。需要注意的是,makefile文件的目标会优先构建文件,因此操作目的需要与文件名有所区分,如果存在与操作目的同名的文件,那么该规则会认为没有必要重新构建该文件,因此不会触发。如果想要避免这种情况,可以在文件中加上.PHONY: <target> (位置随意,有就行,写在最前面或者最后面的比较多见) 值得注意的是如果make命令没有指定目标,那么将会默认为makefile文件的第一个目标
  • 前置条件prerequisites
  • 命令command:一条或者多条shell命令,通常需要能够构建目标文件。如果有多行shell指令的话将会在相互独立的shell中执行,同一行的多条指令将会按照次序在同一个shell中执行;如果想要多行shell也在同一个shell中执行,可以在目标上一行加上.ONESHELL,也可以在每一行后面加上\表示把换行符转义

语法

注释

#在makefile中表示注释

回声

make执行的规则前会将该规则的command(包括注释)打印在终端上,如果不需要哪一行内容打印可以在对应的command前面加上@,通常会在注释前面加上

模式匹配

1
%.o:%.c

这一条指令会将当前目录下的所有的 .c文件 执行command变为对应的 .o 文件
具体命令行怎么写,可以参考后文提到的自动变量之一的$*

变量和赋值符

普通变量定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义时扩展(静态扩展),还是在运行时扩展(动态扩展)

# 定义变量 在执行时扩展,允许递归扩展。作者不是很理解递归扩展的作用,感觉不是很需要使用到,这部分可以理解为普通变量定义
txt = hello world # 定义了一个变量 并赋值为 hello world
# 调用变量
$(txt) # 使用$() 中加入变量的方式,可以实现替代

# 在定义时扩展。常见于定义路径
VARIABLE := value

# 只有在该变量为空时才设置值。
VARIABLE ?= value

# 将值追加到变量的尾端。
VARIABLE += value

内置变量定义

1
2
3
4
5
6
7
# 内置变量
$(CC) #指向当前使用的编译器
$(MAKE) #指向当前使用的Make工具

## 例子
output:
$(CC) -o output input.c

自动变量定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 自动变量
$@ # 指代当前目标,就是Make命令当前构建的那个目标。
$* # 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。可以参考前文模式匹配中的操作
$< # 指代第一个前置条件。

$? # 指代比目标更新的所有前置条件
$^ # 指代所有前置条件

$(@D)、$(@F) # 分别指向 $@ 的目录名和文件名。比如,$@是 src/input.c,那么$(@D) 的值为 src ,$(@F) 的值为 input.c。
$(<D)、$(<F) # 分别指向 $< 的目录名和文件名

# 例子:
a.txt b.txt:
touch $@

dest/%.txt: src/%.txt # 将src目录下的txt文件都拷贝到dest目录下
@[ -e dest ] || mkdir dest # 判断dest目录是否存在,不存在则创建一个,这部分可以学习shell语言了解
cp $< $@

判断与循环

判断语法实例如下:

1
2
3
4
5
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

循环操作实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 数组类型变量定义 循环的写法 shell中的循环写法
LIST = one two three

all:
for i in $(LIST); do \
echo $$i; \
done
# 等同于
all:
for i in one two three; do \
echo $$i; \
done

函数

使用函数的格式:

1
2
3
$(function arguments)
# 或者
${function arguments}

常用的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# subst 函数 替换文本 语法格式如下
$(subst from,to,text) # 其中from to text 可以是变量也可以是实例

# 例子:
$(subst ee,EE,feet on the street) # 将字符串"feet on the street "替换成"fEEt on the strEEt"

# patsubst 函数 用于模式匹配的替换 语法格式如下“
$(patsubst pattern,replacement,text)
$(text: pattern = replacement) # 简写模式,用于替换后缀

# 例子如下:
$(patsubst %.c,%.o,x.c.c bar.c) # 将文件名"x.c.c bar.c",替换成"x.c.o bar.o"。
$(OUTPUT:.js=.min.js) # 将OUTPUT中的后缀名.js 替换为 .min.js

心得体会

我理解的makefile就是一个shell指令的一个批处理操作,对于每一个目标target,就是这一条规则将会生成的文件或者达到的效果。而前置条件,也是一些target,为了区别于目标名,这里将前置条件的target可以记作protarget,就是要实现target就得先实现protarget,就是基于这个思路,可以设置一个指令的批处理操作,而其对于重构target的标准中对于时间戳的考量,则是其能够提高编译效率的一个关键。至于其中提到的command,对于初学者,这一部分是最让人懵逼的,但是仔细一看,需要记忆的也只有一些专有的变量和函数,而且实际的命令写法则是shell脚本语言,所以学习makefile之前最好能先修shell脚本语言,这里可以看看菜鸟教程关于shell脚本语言的介绍,之后作者也会更新相关的知识笔记。

其实对于makefile的使用频率是很少的,因为一个项目往往只需要一个makefile,甚至多个类似的项目使用的makefile几乎一模一样,可以直接copy,而且其在项目初期建立之后往往很少需要改动(如果项目里面的makefile需要常常改动,那这个项目大概率是不适合使用makefile的)。因此makefile实际练手的机会是比较少的,我觉得可以通过先修shell之后,再来结合makefile能够编写出一些shell小脚本,这样makefile和shell都能得到锻炼。