【操作系统】Makefile
make 是一个根据指定的 Shell 命令进行构建的工具——你规定要构建哪个文件、它依赖哪些源文件,当那些文件有变动时,如何重新构建它。
Makefile 格式
<target> : <prerequisites>
[tab] <commands>
目标
- 目标可以是一个文件名,也可以是多个文件名,之间用空格分隔;除了文件名,目标还可以是某个操作的名字。
- 如果 make 命令运行时没有指定目标,默认会执行 Makefile 文件的第一个目标。
- 伪目标
.PHONY
:声明某个“伪目标”后,make 就不会去检查是否存在一个叫做“伪目标”的文件,也不会检查其依赖的修改时间,每次运行都执行对应的命令。
依赖
- 前置文件(依赖)通常是一组文件名,之间用空格分隔。
- 指定了"目标"是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的 last modification 时间戳比目标的时间戳新),"目标"就需要重新构建。
- 依赖可以省略。
all: test.txt
echo hello
test.txt:
echo world > test.txt
若当前目录中没有 test.txt,在 Shell 中执行 make
将会先执行 echo world > test.txt
,随后执行 echo hello
。
规则
-
规则是构建"目标"的具体指令。
-
每行命令在一个单独的 Shell 中执行,这些 Shell 之间没有继承关系;若想让命令间有继承关系:
-
将两行命令写在一行,中间用分号分隔;
-
或在换行符前加反斜杠转义;
all: mkdir a; cd a; \ touch b;
-
或使用
.ONESHELL:
命令,Mac 自带 Make 3.81 不支持.ONESHELL:
,需要:brew instal make
。open ~/zshrc
。- 在
export PATH
中添加/usr/local/bin
和/usr/local/opt/make/libexec/gnubin
,使用:
分隔,如:export PATH=/usr/local/bin:/usr/local/opt/make/libexec/gnubin:$PATH
。
-
-
每行命令之前必须有一个
[tab]
键。如果想用其他键,可以用内置变量.RECIPEPREFIX
声明。
.RECIPEPREFIX=>
.ONESHELL:
all:
>mkdir a
>cd a
>touch b
Makefile 语法
回声
- 正常情况下,make 会打印每条命令,然后再执行,这就叫做回声(echoing)。
- 在命令的前面加上
@
,就可以关闭回声。
通配符
- 用来指定一组符合条件的文件名。
- Makefile 的通配符与 Bash 一致,主要有星号
*
、问号?
和[...]
。- 通配符
?
:匹配一个任意字符。 - 通配符
*
: 匹配0个或任意多个字符,也就是可以匹配任何内容。- 例:对于文件 foo, foo1, foo2, foo10, bar:
rm foo?
删除 foo1 和 foo2。rm foo*
删除除bar
外的所有文件。
- 通配符
[ ]
:匹配中括号中任意一个字符。[-]
代表范围,[^]
代表逻辑非。- 例:
[abc]
匹配a
或b
或c
;[a-z]
匹配所有小写字母;[^0-9]
匹配一个不是数字的字符。
- 例:
- 通配符
模式匹配
- make 命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是
%
。 - 使用匹配符
%
,可以将大量同类型的文件,只用一条规则就完成构建。 - 咕咕咕~
变量和赋值
赋值:
-
VAR=value
:在执行时(引用时)扩展,允许递归扩展。varA = hi varB = $(varA) varA = hello all: @echo $(varB) # 输出 hello
-
VAR:=value
:在定义时(赋值时)扩展。varA := hi varB := $(varA) varA := hello all: @echo $(varB) # 输出 hi
-
VAR?=value
:只有在该变量为空时才设置值。 -
VAR+=value
:将值追加到变量的尾端。
调用:
- 变量需要放在
$( )
之中。 - 调用 Shell 变量,需要在美元符号前,再加一个美元符号,如
$$HOME
。
内置变量
$(CC)
指向当前使用的编译器。$(MAKE)
指向当前使用的 make 工具。
自动变量
$@
:指代当前“目标”。$<
:指代第一个“依赖”。$?
:指代比“目标”新的所有“依赖”。$^
:指代所有“依赖”。$*
:指代匹配符%
匹配的部分。$(@D)
:指向$@
的目录名。$(@F)
:指向$@
的文件名。$(<D)
:指向$<
的目录名。$(<F)
:指向$<
的文件名。
判断和循环
判断:
- 使用
ifeq
、else
和endif
,没有缩进。
all:
ifeq ($(CC),gcc)
@echo hi
else
@echo hello
endif
# 输出 hello
- 使用 Bash 语法,有缩进。
.ONESHELL:
all:
@if [ 1 == 2 ]
@then
@echo hi
@else
@echo hello
@fi
# 输出 hello
循环:
.ONESHELL:
LIST = one two three
all:
@for i in $(LIST)
@do
@echo $$i;
@done
函数
-
调用方法:
$(function arguments)
或${function arguments}
。 -
内置函数:咕咕咕~