>


### Makefile 语法格式 ---- 在 Makefile 中定义目标规则的通用语法是:
[target] : [dependents ....]
[tab][ command ...]

注意:command 前面需要用 tab 而不是空格来隔开。由于执行 make 时每条命令本身都会显示出来,我们还可以在命令前加@符号,来使得只显示命令的执行结果。

下面这个简单的例子定义了一个规则(rule)来生成目标为 hello,这个目标依赖 3 个文件而生成:

hello: main.o factorial.o hello.o
    $(CC) main.o factorial.o hello.o -o hello

上例中要真正生成目标 hello,我们还需要为每个依赖项都定义生成规则(rule)。
隐式规则:对于没有依赖项的目标,make 会直接执行其所定义的命令(commands)


### Makefile 中变量的赋值 ---- Makefile 中给变量赋值的方式有:"=",":="、"?=","+="
符号 含义
“=” 定义一个递归展开的变量。
“:=” 定义一个简单展开的变量。
“?=” 如果该变量为空(也即未被赋值过),则为其赋值。
“+=” 给该变量追加值。

“?=”” 和 “+=” 很好理解,下面介绍一下 “=” 和 “:=” 的区别:

= 使用递归展开来定义变量的意思是,如果变量的值包含了一个对其他变量的引用,那么会递归展开之后再赋给其。

比如:

foo = $(bar)
bar = $(zee)
zee = hello

那么最终, foo 的值就是 hello。

递归展开的方式有两个缺陷:
一是赋给该变量的值不能包含自己,否则会造成无限循环。比如 foo = $(foo) abc,make 命令会检测到这种无限循环的递归展开定义并报错。
二是由于每次引用该变量时都会进行一次递归展开,导致性能很差。

:= 可以克服上述使用 = 来递归展开定义变量所造成的缺陷。

:= 是简单展开,意思是,只会在变量被定义的时候展开一次,而且整个 Makefile 文件中,也只会展开一次。这也就意味着,如果引用的变量的值在之后改变了,那么就不会再被原变量展开了。

比如:

x := foo
y := $(x) bar
x := later

上面的籽粒中 y 最后的值是 foo bar,虽然 x 在 y 定义之后又改变了,但是 y 的值不会再改变,因为它只会在定义的时候对其所引用的 x 展开一次。


### 内置变量(Implicit Variables) Make命令提供一系列内置变量,比如,$(CC) 指向当前使用的编译器,$(MAKE) 指向当前使用的Make工具。这主要是为了跨平台的兼容性,详细的内置变量清单见手册。
output:
    $(CC) -o output input.c

### 自动变量(Automatic Variables) Make 命令还提供一些自动变量,它们的值与当前规则有关。主要有以下几个。

####(1)$@

$@ 指代当前目标,就是 Make 命令当前构建的那个目标。比如,make foo 的 $@ 就指代 foo。

a.txt b.txt:
    touch $@

等同于下面的写法。

a.txt:
    touch a.txt
b.txt:
    touch b.txt

####(2)$<

$< 指代第一个依赖项。比如,规则为 t: p1 p2,那么 $< 就指代 p1。

a.txt: b.txt c.txt
    cp $< $@

等同于下面的写法。

a.txt: b.txt c.txt
    cp b.txt a.txt

####(3)$?

$? 指代比目标更新的所有依赖项,之间以空格分隔。比如,规则为 t: p1 p2,其中 p2 的时间戳比 t 新,$? 就指代 p2。

####(4)$^

$^ 指代所有依赖项,之间以空格分隔。比如,规则为 t: p1 p2,那么 $^ 就指代 p1 p2 。

####(5)$*

$* 指代匹配符 % 匹配的部分, 比如 % 匹配 f1.txt 中的 f1 ,$* 就表示 f1。

####(6)$(@D) 和 $(@F)

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

####(7)$(<D) 和 $(<F)

$(<D) 和 $(<F) 分别指向 $< 的目录名和文件名。

所有的自动变量清单,请看手册。下面是自动变量的一个例子。

dest/%.txt: src/%.txt
    @[ -d dest ] || mkdir dest
    cp $< $@

上面代码将 src 目录下的 txt 文件,拷贝到 dest 目录下。首先判断 dest 目录是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), $@ 指代目标文件(dest/%.txt)。

使用 define ... endef 来定义一系列命令(Canned Command Sequences)

当一组命令可以用来作用于多个 targets 来使用时,我们可以使用 define 指令来把他们定义到一个变量中,然后通过这个变量来引用和执行这些命令。

define cc =
@file Makefile
@date -u +%Y/%m/%d
endef

info:
    $(cc)

##### 使用空命令 ---- 虽然给一个 target 定义一个空命令会什么也不做,但有时候这么定义也是有用的。 定义的方式就是在目标冒号后面直接添加一个 ; 就可以了
target: ;

##### 条件控制 ---- Makefile 中可以使用的条件指令有:
  • ifeq
  • ifneq
  • ifdef
  • ifndef
  • else
  • endif

条件语句的语法:

conditional-directive
   text-if-true
else
   text-if-false
endif

ifeq (arg1, arg2)ifeq 'arg1', 'arg2' 效果相同。

注意:条件比较语句之前不能有 Tab,必须是在每行的第一个字符开始。

例子

ifeq ($(CC),gcc)
  libs=$(libs_for_gcc)
else
  libs=$(normal_libs)
endif

上面代码判断当前编译器是否 gcc ,然后指定不同的库文件。

LIST = one two three
all:
    for i in $(LIST); do \
        echo $$i; \
    done

等同于

all:
    for i in one two three; do \
        echo $i; \
    done

上面代码的运行结果。

one
two
three

函数

Makefile 还可以使用函数,格式如下。

$(function arguments)

或者

${function arguments}

Makefile 提供了许多内置函数,可供调用。下面是几个常用的内置函数。

####(1)shell 函数

shell 函数用来执行 shell 命令

srcfiles := $(shell echo src/{00..99}.txt)

或者

PWD:=$(shell pwd)
BUILD:=$(shell date +"%Y%m%d%H%M%S")
SOURCE:=$(shell ls *.go)

all:
    @echo $(PWD)
    @echo $(BUILD)
    @echo $(SOURCE)
    @echo "done"

####(2)wildcard 函数

wildcard 函数用来在 Makefile 中,替换 Bash 的通配符。

srcfiles := $(wildcard src/*.txt)

####(3)subst 函数

subst 函数用来文本替换,格式如下:

$(subst from,to,text)

下面的例子将字符串 “feet on the street” 替换成 “fEEt on the strEEt”。

$(subst ee,EE,feet on the street)

下面是一个稍微复杂的例子。

comma:= ,
empty:=
# space变量用两个空变量作为标识符,当中是一个空格
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now `a,b,c'.

####(4)patsubst函数

patsubst 函数用于模式匹配的替换,格式如下。

$(patsubst pattern,replacement,text)

下面的例子将文件名 “x.c.c bar.c”,替换成 “x.c.o bar.o”。

$(patsubst %.c,%.o,x.c.c bar.c)

####(5)替换后缀名

替换后缀名函数的写法是:变量名 + 冒号 + 后缀名替换规则。它实际上 patsubst 函数的一种简写形式。

min: $(OUTPUT:.js=.min.js)

上面代码的意思是,将变量OUTPUT中的后缀名 .js 全部替换成 .min.js 。


##### make 命令的默认执行顺序 ----

执行 make 命令时默认会执行第一个不以 . 开头的目标。
比如第一行定义的目标为 haha,那么如果直接执行 make 的话,就会执行 haha 。
By default, it begins by processing the first target that does not begin with a ‘.’ aka the default goal; to do that, it may have to process other targets - specifically, ones the first target depends on. The GNU make manual covers all this stuff, and is a surprisingly easy and informative read.

also note, calling the first target all is just a convention, there is no mandatory requirement we declare the first target as all.

Makefile 中通常会支持 make all, make install, make clean,这只是一种惯例,而不是强制要求,我们可以完全使用其他的目标名字来代替 all, install, clean 等。


##### 在 Makefile 中调用 make 命令 ---- 当我们工作在一个大型工程中,每个子系统都有自己的 Makefile 文件,那么我们可能需要在最外层的 Makefile 文件中去执行 make 命令从而递归调用处于子目录中的 Makefile 文件。 比如,我们有个子目录叫做 `subdir`,这个子目录有自己的 Makefile,如果想在当前目录中的 Makefile 中调用 make 命令去执行 `subdir` 中的 Makefile,可以这样做:
subsystem:
    cd subdir && make

或者

subsystem:
    make -C subdir

上述两者等效。

提示:在执行 make 时,屏幕会打印 make[1]: Entering directory ‘…/subdir’ 和 Leaving directory ‘../subdir’ 这样的信息,如果不希望打印这些信息,可以在 make 命令后添加 -s 选项来屏蔽这些输出。
比如执行 make -s 或者在 Makefile 中改成 @make -s -C subdir。更多信息可参考这个

如果不想显示除了出错和警告之外的任何信息。如果 Makefile 指定的目标中执行了 shell 命令打印结果到屏幕,那么那些命令的结果也不会显示。那么可以在 make 命令后添加 > /dev/null 来屏蔽标准输出。

例如:

subsystem:
    @make -C subdir > /dev/null

##### 在 Makefile 中使用系统环境变量 ---- Makefile 中可以直接引用系统环境变量,但是如果在 Makefile 中定义了与系统环境变量同名的变量,那么会覆盖系统环境变量。如果定义的变量不在系统环境变量中,那么这些变量只会在当前 Makefile 中起作用。 可以把一些常用的变量定义在一个单独的文件中,然后在 Makefile 中使用 include 关键字把他们引进来。
include envfile
export $(shell sed 's/=.*//' envfile)

test:
    env

##### 在递归调用 sub-make 时传递变量 ---- 在最外层的 Makefile 文件中定义的变量可能需要传递给子目录中的 Makefile 使用,要达到此效果,我们需要在 Makefile 中使用 export 关键字显示的导出变量到环境变量中,这样子 make 就能访问到这些变量了。 同理,如果不想变量传递,可以使用 unexport。

例子:

variable := value
export variable

或者

export variable := value

##### .PHONY 的作用 ----

由于执行 make clean 的时候, clean 目标没有任何依赖, make 则认为已经到根上了,如果正好当前目录中有一个文件其名称正好是叫 clean,那么会导致 make clean 什么也不做。
加 .PHONY 之后,就告诉 make 这个 clean 目标是假的,这时候 make clean 就会无视当前目录是否有 clean 文件存在,而为这个目标强制执行 clean 目标所指定的命令。

简单来说,.PHONY 修饰的目标是只有规则而没有依赖的,主要为了防止当前目录中有跟目标同名的文件。

其他
执行 make 时可能会报错:”make: warning: Clock skew detected. Your build may be incomplete.”
这可能是由于你在挂载的目录或者文件系统中新建文件(比如在 Linux 系统中挂载了一个 Windows 系统下的目录)导致的时间不一致的情况,
一般,这种情况下,只需要执行 touch Makefile 命令就可以解决。




Reference
https://ftp.gnu.org/old-gnu/Manuals/make-3.80/html_mono/make.html