目录

Makefile

参考:



概述

代码编程可执行文件,叫做编译(compile)。先编译这个,再编译那个,叫做构建(build)。

make 是常用的构建工具。构建规则都写在 makefile 里,要学会如何make命令,就必须学会编写makefile文件。




Makefile文件格式


概述

Makefile文件由一系列规则构成,每条规则的形式如下:

  • 目标(target):必须
  • 前置条件(prerequisites): 可选,但前置条件和命令必须至少存在一个。
  • tab
  • 命令(commands):可选,但前置条件和命令必须至少存在一个。
1
2
<target>: <prerequisites>
[tab] <commands>

每条规则就明确两件事,构建目标的前置条件是什么,以及如何构建。



目标

目标通常是文件名(多个文件之间用空格分隔),指明make命令所要构建的对象。目标还可以是某个操作的名字,这称为 伪目标 (phony target)。

1
2
clean:
	rm *.o

上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于伪目标。

1
make clean

但是,如果在当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为make发现clean文件已存在,就认为没有必须要重新构建了,就不会执行rm命令。

为了避免这种情况,可以明确声明clean是伪目标。声明伪目标之后,make就不会去检查是否存在一个叫做clean的文件。

1
2
3
4
# 为声明伪目标
.PHONY: clean
clean:
	rm *.o temp

如果make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。

1
make


前置条件

前置条件通常是一组文件名,用空格分隔。它指定了目标是否需要重新构建的判断标准,只要有一个前置条件不存在,或者有过更新,目标就需要重新构建。

1
2
result.txt: source.txt
	cp source.txt result.txt


命令

命令表示如何更新目标,它是构建目标的具体指令。

每行命令之前必须有tab键,如果想用其它键,可以使用内置的 .RECIPEPREFIX 来声明。

1
2
3
4
# 使用 > 替换 tab
.RECIPEPREFIX = >
all:
> echo Hello, world

需要注意的是,每行命令在一个单独的shell中执行,这些shell之间没有继承关系。不过可以多个命令写在一行,或使用反斜线(\),或使用 .ONESHELL 命令。

1
2
3
var-lost:
    export foo=bar
    echo "foo=[$$foo]"
1
2
var-kept:
    export foo=bar; echo "foo=[$$foo]"
1
2
3
4
.ONESHELL:
var-kept:
    export foo=bar; 
    echo "foo=[$$foo]"



Makefile文件语法


注释

1
2
3
4
# 这里是注释
result.txt: source.txt
    # 这是注释
    cp source.txt result.txt # 这也是注释


回声

默认情况下,make会答应每条命令,然后再执行,这叫做回声(echoing)。

可以在命令前加上 @ ,来关闭回声。

由于在构建过程中,需要了解当前执行详情,所以通常只在纯注释和显示的echo命令前面机上@

1
2
3
test:
    @# 这是测试
    @echo TODO


通配符

Makefile的通配符和Bash一直,主要有 *, ?, [...]



模式匹配

make命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是%

使用匹配符,可以将大量同类型的文件,只用一条规则就完成构建。

1
%.o: %.c


变量和赋值

Makefile允许使用等号来自定义变量。

调用shell变量,需要在美元符号前多加一个美元符号。

1
2
3
4
5
6
txt = Hello World
test:
    @echo $(txt)

test2:
    @echo $$HOME

makefile还提供了四个赋值运算符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
VARIABLE = value
# 在执行时扩展,允许递归扩展。

VARIABLE := value
# 在定义时扩展。

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

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


内置变量

make提供了一系列内置变量,比如, $(CC) 指向当前使用的编译器, $(MAKE) 指向当前使用的make工具。

其它内置变量,请参考文档



自动变量

make命令还提供一些自动变量,它们的值与当前的规则有关。

1
2
3
4
5
6
7
8
9
# $@ 指代当前目标

a.txt b.txt:
    touch $@
# 等价于
a.txt:
    touch a.txt
b.txt:
    touch b.txt

1
2
3
4
5
6
7
# $< 指代第一个前置条件

a.txt: b.txt c.txt
    cp $< $@
# 等价于
a.txt: b.txt c.txt
    cp b.txt a.txt

1
# $? 指代比目标更新的所有前置条件, 以空格分隔。

1
# $^ 指代所有前置条件,之间以空格分隔。

1
# $* 指代匹配符 % 匹配的部分

1
# $(@D) 和 $(@F) 分别指向 $@ 的目录名和文件名

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


判断和循环

Makefile使用Bash语法,完成判断和循环。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
LIST = one two three
all:
    for i in $(LIST); do \
        echo $$i; \
    done
# 等同于
all:
    for i in one two three; do \
        echo $i; \
    done


函数

Makefile还可以使用函数。

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

1
2
# shell 函数用来执行 shell 命令
srcfiles := $(shell echo src/{00..99}.txt)

1
2
# wildcard 函数用来在 Makefile 中,替换 Bash 的通配符。
srcfiles := $(wildcard src/*.txt)

1
2
# subst 函数用来文本替换
$(subst from,to,text)

1
2
# patsubst 函数用于模式匹配的替换
$(patsubst pattern,replacement,text)

1
2
# 替换后缀名函数的写法是:变量名 + 冒号 + 后缀名替换规则
min: $(OUTPUT:.js=.min.js)



Makefile实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 执行多个目标
.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
        rm program

cleanobj :
        rm *.o

cleandiff :
        rm *.diff