规则

Makefile 中规则(rule)由 3 个部分组成:

target: dependencies
    command

依赖可以是具体的文件或者是其他目标。

增量构建原理

Make 进行增量构建的原理非常简单,利用了构建的产物和依赖的时间戳。

每个目标都存在直接或者间接的依赖,最终都会具体到一些文件。当我们选择构建目标时,make 会检查 target 的产物的时间戳是否比它的依赖的时间戳旧,如果旧的话,就说明上次构建后,又新修改了依赖,那么就需要再次构建。

可以想见,如果依赖不全的话,那么很容易导致增量构建忽视掉了本应该重新构建的文件。这种现象多出现在头文件中,因为按理说头文件中没有实现,那么无需作为依赖,而当其中包含具体实现的时候,那么就需要纳入依赖文件中了。

变量

基础使用

在 Makefile 中,我们用 =, ?=, +=, := 来对变量进行赋值,变量的类型应该都是字符串,所以是可以有空格出现且不需要引号的。并且等号两边也是可以有空格的。

我们用 $(VAR) 的形式使用变量,示例如下:

VAR = Hello world
 
all:
    echo$(VAR) # Hello world

需要注意的是,虽然 Makefile 的 command 类似于 ShellScript ,但是有很多用法都不一样,比如说:

  • 赋值语法:shellscript 不允许等号两侧有空格,makfile 允许。
  • 赋值符号:shellscript 是没有 ?=, := 的,只有 =, +=
  • 变量使用:shellscript 使用变量为 VAR ,而 $(Command) 表示对于 Command 求值并引用。

赋值

条件赋值是指一个变量如果没有被定义过,就直接给它赋值;如果之前被定义过,那么这条赋值语句就什么都不做。

VAR = hello
VAR ?= hi
 
all:
    echo$(VAR) # hello

立即变量和延迟变量

使用 = 进行赋值的变量被称为延迟变量,它是在使用时才去求值。以下面为例, VAR2 的值是被 VAR1 赋予的,在给 VAR2 赋值后, VAR1 又发生了变化,延迟变量 VAR2 最后打印的是更改后的结果。

VAR1 = Hello
VAR2 =$(VAR1)
VAR1 = Hi
 
all:
    echo$(VAR2) # Hi

而如果是立即赋值,那么就会打印更改前的结果。

VAR1 = Hello
VAR2 :=$(VAR1)
VAR1 = Hi
 
all:
    echo$(VAR2) # Hello

立即变量和延迟变量对于字面量没有差别。主要在于像 VAR2 这种的间接量才会存在差异。一般来说,这两种变量的差异类似于“编译时”和“运行时”的差异,如果在运行时有变化的变量(比如说 VAR1 的两次赋值,或者执行某个指令,如 $(pwd) ),那么应该使用延迟变量,否则应该使用立即变量。

自动变量

自动变量是局部变量,作用域范围在当前的规则内,有如下种类:

  • $@ :目标
  • $^ :所有目标依赖
  • $< :目标依赖列表中的第一个依赖
  • $? :所有目标依赖中被修改过的文件

函数

基本使用

对于内建的函数,使用如下方式调用:

$(func arg1, arg2, arg3)

对于自定义的函数,需要使用 call 进行间接调用:

define func
    @echo "pram1 =$(0)"
    @echo "pram2 =$(1)"
endef
all:
   $(call func, hello, zhaixue.cc)

常见函数

wildcard

wildcard 就是“通配符”的意思,也可以翻译成“外卡”。据说起源是牌类游戏的概念,wildcard 类似与 UNO 中的万能牌,也就是和谁都可以搭配的牌,被用作通配符非常有道理。

Makefile 中使用通配符并没有 shell 中那么自然,比较像 shell 的情况只出现在依赖和命令中。如下所示:

*.o: *.c
    gcc -c$^
 
clean:
    rm -f *.o

但是如果希望在变量中使用,那么就需要用 wildcard 函数了:

SRC  =$(wildcard *.c)
HEAD =$(wildcard *.h)

patsubst

patsubst 函数主要用来模式替换:使用通配符 % 代表一个单词中的若干字符,在 PATTERNREPLACEMENT 如果都包含这个通配符,表示两者表示的是相同的若干个字符,并执行替换操作。

$(patsubst PATTERN, REPLACEMENT, TEXT)

shell

在 makefile 中运行 shell 命令需要使用 shell 函数,如下所示:

current_path =$(shell pwd)